Contiki 2.6
|
00001 /* 00002 * Copyright (c) 2005, Swedish Institute of Computer Science 00003 * All rights reserved. 00004 * 00005 * Redistribution and use in source and binary forms, with or without 00006 * modification, are permitted provided that the following conditions 00007 * are met: 00008 * 1. Redistributions of source code must retain the above copyright 00009 * notice, this list of conditions and the following disclaimer. 00010 * 2. Redistributions in binary form must reproduce the above copyright 00011 * notice, this list of conditions and the following disclaimer in the 00012 * documentation and/or other materials provided with the distribution. 00013 * 3. Neither the name of the Institute nor the names of its contributors 00014 * may be used to endorse or promote products derived from this software 00015 * without specific prior written permission. 00016 * 00017 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 00018 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 00019 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 00020 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 00021 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 00022 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 00023 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 00024 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 00025 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 00026 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 00027 * SUCH DAMAGE. 00028 * 00029 * This file is part of the Contiki operating system. 00030 * 00031 * @(#)$Id: elfloader.c,v 1.10 2009/02/27 14:28:02 nvt-se Exp $ 00032 */ 00033 00034 #include "contiki.h" 00035 00036 #include "loader/elfloader.h" 00037 #include "loader/elfloader-arch.h" 00038 00039 #include "cfs/cfs.h" 00040 #include "loader/symtab.h" 00041 00042 #include <stddef.h> 00043 #include <string.h> 00044 #include <stdio.h> 00045 00046 #define DEBUG 0 00047 #if DEBUG 00048 #include <stdio.h> 00049 #define PRINTF(...) printf(__VA_ARGS__) 00050 #else 00051 #define PRINTF(...) do {} while (0) 00052 #endif 00053 00054 #define EI_NIDENT 16 00055 00056 00057 struct elf32_ehdr { 00058 unsigned char e_ident[EI_NIDENT]; /* ident bytes */ 00059 elf32_half e_type; /* file type */ 00060 elf32_half e_machine; /* target machine */ 00061 elf32_word e_version; /* file version */ 00062 elf32_addr e_entry; /* start address */ 00063 elf32_off e_phoff; /* phdr file offset */ 00064 elf32_off e_shoff; /* shdr file offset */ 00065 elf32_word e_flags; /* file flags */ 00066 elf32_half e_ehsize; /* sizeof ehdr */ 00067 elf32_half e_phentsize; /* sizeof phdr */ 00068 elf32_half e_phnum; /* number phdrs */ 00069 elf32_half e_shentsize; /* sizeof shdr */ 00070 elf32_half e_shnum; /* number shdrs */ 00071 elf32_half e_shstrndx; /* shdr string index */ 00072 }; 00073 00074 /* Values for e_type. */ 00075 #define ET_NONE 0 /* Unknown type. */ 00076 #define ET_REL 1 /* Relocatable. */ 00077 #define ET_EXEC 2 /* Executable. */ 00078 #define ET_DYN 3 /* Shared object. */ 00079 #define ET_CORE 4 /* Core file. */ 00080 00081 struct elf32_shdr { 00082 elf32_word sh_name; /* section name */ 00083 elf32_word sh_type; /* SHT_... */ 00084 elf32_word sh_flags; /* SHF_... */ 00085 elf32_addr sh_addr; /* virtual address */ 00086 elf32_off sh_offset; /* file offset */ 00087 elf32_word sh_size; /* section size */ 00088 elf32_word sh_link; /* misc info */ 00089 elf32_word sh_info; /* misc info */ 00090 elf32_word sh_addralign; /* memory alignment */ 00091 elf32_word sh_entsize; /* entry size if table */ 00092 }; 00093 00094 /* sh_type */ 00095 #define SHT_NULL 0 /* inactive */ 00096 #define SHT_PROGBITS 1 /* program defined information */ 00097 #define SHT_SYMTAB 2 /* symbol table section */ 00098 #define SHT_STRTAB 3 /* string table section */ 00099 #define SHT_RELA 4 /* relocation section with addends*/ 00100 #define SHT_HASH 5 /* symbol hash table section */ 00101 #define SHT_DYNAMIC 6 /* dynamic section */ 00102 #define SHT_NOTE 7 /* note section */ 00103 #define SHT_NOBITS 8 /* no space section */ 00104 #define SHT_REL 9 /* relation section without addends */ 00105 #define SHT_SHLIB 10 /* reserved - purpose unknown */ 00106 #define SHT_DYNSYM 11 /* dynamic symbol table section */ 00107 #define SHT_LOPROC 0x70000000 /* reserved range for processor */ 00108 #define SHT_HIPROC 0x7fffffff /* specific section header types */ 00109 #define SHT_LOUSER 0x80000000 /* reserved range for application */ 00110 #define SHT_HIUSER 0xffffffff /* specific indexes */ 00111 00112 struct elf32_rel { 00113 elf32_addr r_offset; /* Location to be relocated. */ 00114 elf32_word r_info; /* Relocation type and symbol index. */ 00115 }; 00116 00117 struct elf32_sym { 00118 elf32_word st_name; /* String table index of name. */ 00119 elf32_addr st_value; /* Symbol value. */ 00120 elf32_word st_size; /* Size of associated object. */ 00121 unsigned char st_info; /* Type and binding information. */ 00122 unsigned char st_other; /* Reserved (not used). */ 00123 elf32_half st_shndx; /* Section index of symbol. */ 00124 }; 00125 00126 #define ELF32_R_SYM(info) ((info) >> 8) 00127 #define ELF32_R_TYPE(info) ((unsigned char)(info)) 00128 00129 struct relevant_section { 00130 unsigned char number; 00131 unsigned int offset; 00132 char *address; 00133 }; 00134 00135 char elfloader_unknown[30]; /* Name that caused link error. */ 00136 00137 struct process * const * elfloader_autostart_processes; 00138 00139 static struct relevant_section bss, data, rodata, text; 00140 00141 static const unsigned char elf_magic_header[] = 00142 {0x7f, 0x45, 0x4c, 0x46, /* 0x7f, 'E', 'L', 'F' */ 00143 0x01, /* Only 32-bit objects. */ 00144 0x01, /* Only LSB data. */ 00145 0x01, /* Only ELF version 1. */ 00146 }; 00147 00148 /*---------------------------------------------------------------------------*/ 00149 static void 00150 seek_read(int fd, unsigned int offset, char *buf, int len) 00151 { 00152 cfs_seek(fd, offset, CFS_SEEK_SET); 00153 cfs_read(fd, buf, len); 00154 #if DEBUG 00155 { 00156 int i; 00157 PRINTF("seek_read: Read len %d from offset %d\n", 00158 len, offset); 00159 for(i = 0; i < len; ++i ) { 00160 PRINTF("%02x ", buf[i]); 00161 } 00162 printf("\n"); 00163 } 00164 #endif /* DEBUG */ 00165 } 00166 /*---------------------------------------------------------------------------*/ 00167 /* 00168 static void 00169 seek_write(int fd, unsigned int offset, char *buf, int len) 00170 { 00171 cfs_seek(fd, offset, CFS_SEEK_SET); 00172 cfs_write(fd, buf, len); 00173 } 00174 */ 00175 /*---------------------------------------------------------------------------*/ 00176 static void * 00177 find_local_symbol(int fd, const char *symbol, 00178 unsigned int symtab, unsigned short symtabsize, 00179 unsigned int strtab) 00180 { 00181 struct elf32_sym s; 00182 unsigned int a; 00183 char name[30]; 00184 struct relevant_section *sect; 00185 00186 for(a = symtab; a < symtab + symtabsize; a += sizeof(s)) { 00187 seek_read(fd, a, (char *)&s, sizeof(s)); 00188 00189 if(s.st_name != 0) { 00190 seek_read(fd, strtab + s.st_name, name, sizeof(name)); 00191 if(strcmp(name, symbol) == 0) { 00192 if(s.st_shndx == bss.number) { 00193 sect = &bss; 00194 } else if(s.st_shndx == data.number) { 00195 sect = &data; 00196 } else if(s.st_shndx == text.number) { 00197 sect = &text; 00198 } else { 00199 return NULL; 00200 } 00201 return &(sect->address[s.st_value]); 00202 } 00203 } 00204 } 00205 return NULL; 00206 } 00207 /*---------------------------------------------------------------------------*/ 00208 static int 00209 relocate_section(int fd, 00210 unsigned int section, unsigned short size, 00211 unsigned int sectionaddr, 00212 char *sectionbase, 00213 unsigned int strs, 00214 unsigned int strtab, 00215 unsigned int symtab, unsigned short symtabsize, 00216 unsigned char using_relas) 00217 { 00218 /* sectionbase added; runtime start address of current section */ 00219 struct elf32_rela rela; /* Now used both for rel and rela data! */ 00220 int rel_size = 0; 00221 struct elf32_sym s; 00222 unsigned int a; 00223 char name[30]; 00224 char *addr; 00225 struct relevant_section *sect; 00226 00227 /* determine correct relocation entry sizes */ 00228 if(using_relas) { 00229 rel_size = sizeof(struct elf32_rela); 00230 } else { 00231 rel_size = sizeof(struct elf32_rel); 00232 } 00233 00234 for(a = section; a < section + size; a += rel_size) { 00235 seek_read(fd, a, (char *)&rela, rel_size); 00236 seek_read(fd, 00237 symtab + sizeof(struct elf32_sym) * ELF32_R_SYM(rela.r_info), 00238 (char *)&s, sizeof(s)); 00239 if(s.st_name != 0) { 00240 seek_read(fd, strtab + s.st_name, name, sizeof(name)); 00241 PRINTF("name: %s\n", name); 00242 addr = (char *)symtab_lookup(name); 00243 /* ADDED */ 00244 if(addr == NULL) { 00245 PRINTF("name not found in global: %s\n", name); 00246 addr = find_local_symbol(fd, name, symtab, symtabsize, strtab); 00247 PRINTF("found address %p\n", addr); 00248 } 00249 if(addr == NULL) { 00250 if(s.st_shndx == bss.number) { 00251 sect = &bss; 00252 } else if(s.st_shndx == data.number) { 00253 sect = &data; 00254 } else if(s.st_shndx == rodata.number) { 00255 sect = &rodata; 00256 } else if(s.st_shndx == text.number) { 00257 sect = &text; 00258 } else { 00259 PRINTF("elfloader unknown name: '%30s'\n", name); 00260 memcpy(elfloader_unknown, name, sizeof(elfloader_unknown)); 00261 elfloader_unknown[sizeof(elfloader_unknown) - 1] = 0; 00262 return ELFLOADER_SYMBOL_NOT_FOUND; 00263 } 00264 addr = sect->address; 00265 } 00266 } else { 00267 if(s.st_shndx == bss.number) { 00268 sect = &bss; 00269 } else if(s.st_shndx == data.number) { 00270 sect = &data; 00271 } else if(s.st_shndx == rodata.number) { 00272 sect = &rodata; 00273 } else if(s.st_shndx == text.number) { 00274 sect = &text; 00275 } else { 00276 return ELFLOADER_SEGMENT_NOT_FOUND; 00277 } 00278 00279 addr = sect->address; 00280 } 00281 00282 if(!using_relas) { 00283 /* copy addend to rela structure */ 00284 seek_read(fd, sectionaddr + rela.r_offset, (char *)&rela.r_addend, 4); 00285 } 00286 00287 elfloader_arch_relocate(fd, sectionaddr, sectionbase, &rela, addr); 00288 } 00289 return ELFLOADER_OK; 00290 } 00291 /*---------------------------------------------------------------------------*/ 00292 static void * 00293 find_program_processes(int fd, 00294 unsigned int symtab, unsigned short size, 00295 unsigned int strtab) 00296 { 00297 struct elf32_sym s; 00298 unsigned int a; 00299 char name[30]; 00300 00301 for(a = symtab; a < symtab + size; a += sizeof(s)) { 00302 seek_read(fd, a, (char *)&s, sizeof(s)); 00303 00304 if(s.st_name != 0) { 00305 seek_read(fd, strtab + s.st_name, name, sizeof(name)); 00306 if(strcmp(name, "autostart_processes") == 0) { 00307 return &data.address[s.st_value]; 00308 } 00309 } 00310 } 00311 return NULL; 00312 /* return find_local_symbol(fd, "autostart_processes", symtab, size, strtab); */ 00313 } 00314 /*---------------------------------------------------------------------------*/ 00315 void 00316 elfloader_init(void) 00317 { 00318 elfloader_autostart_processes = NULL; 00319 } 00320 /*---------------------------------------------------------------------------*/ 00321 #if 0 00322 static void 00323 print_chars(unsigned char *ptr, int num) 00324 { 00325 int i; 00326 for(i = 0; i < num; ++i) { 00327 PRINTF("%d", ptr[i]); 00328 if(i == num - 1) { 00329 PRINTF("\n"); 00330 } else { 00331 PRINTF(", "); 00332 } 00333 } 00334 } 00335 #endif /* 0 */ 00336 /*---------------------------------------------------------------------------*/ 00337 int 00338 elfloader_load(int fd) 00339 { 00340 struct elf32_ehdr ehdr; 00341 struct elf32_shdr shdr; 00342 struct elf32_shdr strtable; 00343 unsigned int strs; 00344 unsigned int shdrptr; 00345 unsigned int nameptr; 00346 char name[12]; 00347 00348 int i; 00349 unsigned short shdrnum, shdrsize; 00350 00351 unsigned char using_relas = -1; 00352 unsigned short textoff = 0, textsize, textrelaoff = 0, textrelasize; 00353 unsigned short dataoff = 0, datasize, datarelaoff = 0, datarelasize; 00354 unsigned short rodataoff = 0, rodatasize, rodatarelaoff = 0, rodatarelasize; 00355 unsigned short symtaboff = 0, symtabsize; 00356 unsigned short strtaboff = 0, strtabsize; 00357 unsigned short bsssize = 0; 00358 00359 struct process **process; 00360 int ret; 00361 00362 elfloader_unknown[0] = 0; 00363 00364 /* The ELF header is located at the start of the buffer. */ 00365 seek_read(fd, 0, (char *)&ehdr, sizeof(ehdr)); 00366 00367 /* print_chars(ehdr.e_ident, sizeof(elf_magic_header)); 00368 print_chars(elf_magic_header, sizeof(elf_magic_header));*/ 00369 /* Make sure that we have a correct and compatible ELF header. */ 00370 if(memcmp(ehdr.e_ident, elf_magic_header, sizeof(elf_magic_header)) != 0) { 00371 PRINTF("ELF header problems\n"); 00372 return ELFLOADER_BAD_ELF_HEADER; 00373 } 00374 00375 /* Grab the section header. */ 00376 shdrptr = ehdr.e_shoff; 00377 seek_read(fd, shdrptr, (char *)&shdr, sizeof(shdr)); 00378 00379 /* Get the size and number of entries of the section header. */ 00380 shdrsize = ehdr.e_shentsize; 00381 shdrnum = ehdr.e_shnum; 00382 00383 PRINTF("Section header: size %d num %d\n", shdrsize, shdrnum); 00384 00385 /* The string table section: holds the names of the sections. */ 00386 seek_read(fd, ehdr.e_shoff + shdrsize * ehdr.e_shstrndx, 00387 (char *)&strtable, sizeof(strtable)); 00388 00389 /* Get a pointer to the actual table of strings. This table holds 00390 the names of the sections, not the names of other symbols in the 00391 file (these are in the sybtam section). */ 00392 strs = strtable.sh_offset; 00393 00394 PRINTF("Strtable offset %d\n", strs); 00395 00396 /* Go through all sections and pick out the relevant ones. The 00397 ".text" segment holds the actual code from the ELF file, the 00398 ".data" segment contains initialized data, the ".bss" segment 00399 holds the size of the unitialized data segment. The ".rel[a].text" 00400 and ".rel[a].data" segments contains relocation information for the 00401 contents of the ".text" and ".data" segments, respectively. The 00402 ".symtab" segment contains the symbol table for this file. The 00403 ".strtab" segment points to the actual string names used by the 00404 symbol table. 00405 00406 In addition to grabbing pointers to the relevant sections, we 00407 also save the section number for resolving addresses in the 00408 relocator code. 00409 */ 00410 00411 00412 /* Initialize the segment sizes to zero so that we can check if 00413 their sections was found in the file or not. */ 00414 textsize = textrelasize = datasize = datarelasize = 00415 rodatasize = rodatarelasize = symtabsize = strtabsize = 0; 00416 00417 bss.number = data.number = rodata.number = text.number = -1; 00418 00419 shdrptr = ehdr.e_shoff; 00420 for(i = 0; i < shdrnum; ++i) { 00421 00422 seek_read(fd, shdrptr, (char *)&shdr, sizeof(shdr)); 00423 00424 /* The name of the section is contained in the strings table. */ 00425 nameptr = strs + shdr.sh_name; 00426 seek_read(fd, nameptr, name, sizeof(name)); 00427 PRINTF("Section shdrptr 0x%x, %d + %d type %d\n", 00428 shdrptr, 00429 strs, shdr.sh_name, 00430 (int)shdr.sh_type); 00431 /* Match the name of the section with a predefined set of names 00432 (.text, .data, .bss, .rela.text, .rela.data, .symtab, and 00433 .strtab). */ 00434 /* added support for .rodata, .rel.text and .rel.data). */ 00435 00436 if(shdr.sh_type == SHT_SYMTAB/*strncmp(name, ".symtab", 7) == 0*/) { 00437 PRINTF("symtab\n"); 00438 symtaboff = shdr.sh_offset; 00439 symtabsize = shdr.sh_size; 00440 } else if(shdr.sh_type == SHT_STRTAB/*strncmp(name, ".strtab", 7) == 0*/) { 00441 PRINTF("strtab\n"); 00442 strtaboff = shdr.sh_offset; 00443 strtabsize = shdr.sh_size; 00444 } else if(strncmp(name, ".text", 5) == 0) { 00445 textoff = shdr.sh_offset; 00446 textsize = shdr.sh_size; 00447 text.number = i; 00448 text.offset = textoff; 00449 } else if(strncmp(name, ".rel.text", 9) == 0) { 00450 using_relas = 0; 00451 textrelaoff = shdr.sh_offset; 00452 textrelasize = shdr.sh_size; 00453 } else if(strncmp(name, ".rela.text", 10) == 0) { 00454 using_relas = 1; 00455 textrelaoff = shdr.sh_offset; 00456 textrelasize = shdr.sh_size; 00457 } else if(strncmp(name, ".data", 5) == 0) { 00458 dataoff = shdr.sh_offset; 00459 datasize = shdr.sh_size; 00460 data.number = i; 00461 data.offset = dataoff; 00462 } else if(strncmp(name, ".rodata", 7) == 0) { 00463 /* read-only data handled the same way as regular text section */ 00464 rodataoff = shdr.sh_offset; 00465 rodatasize = shdr.sh_size; 00466 rodata.number = i; 00467 rodata.offset = rodataoff; 00468 } else if(strncmp(name, ".rel.rodata", 11) == 0) { 00469 /* using elf32_rel instead of rela */ 00470 using_relas = 0; 00471 rodatarelaoff = shdr.sh_offset; 00472 rodatarelasize = shdr.sh_size; 00473 } else if(strncmp(name, ".rela.rodata", 12) == 0) { 00474 using_relas = 1; 00475 rodatarelaoff = shdr.sh_offset; 00476 rodatarelasize = shdr.sh_size; 00477 } else if(strncmp(name, ".rel.data", 9) == 0) { 00478 /* using elf32_rel instead of rela */ 00479 using_relas = 0; 00480 datarelaoff = shdr.sh_offset; 00481 datarelasize = shdr.sh_size; 00482 } else if(strncmp(name, ".rela.data", 10) == 0) { 00483 using_relas = 1; 00484 datarelaoff = shdr.sh_offset; 00485 datarelasize = shdr.sh_size; 00486 } else if(strncmp(name, ".bss", 4) == 0) { 00487 bsssize = shdr.sh_size; 00488 bss.number = i; 00489 bss.offset = 0; 00490 } 00491 00492 /* Move on to the next section header. */ 00493 shdrptr += shdrsize; 00494 } 00495 00496 if(symtabsize == 0) { 00497 return ELFLOADER_NO_SYMTAB; 00498 } 00499 if(strtabsize == 0) { 00500 return ELFLOADER_NO_STRTAB; 00501 } 00502 if(textsize == 0) { 00503 return ELFLOADER_NO_TEXT; 00504 } 00505 00506 PRINTF("before allocate ram\n"); 00507 bss.address = (char *)elfloader_arch_allocate_ram(bsssize + datasize); 00508 data.address = (char *)bss.address + bsssize; 00509 PRINTF("before allocate rom\n"); 00510 text.address = (char *)elfloader_arch_allocate_rom(textsize + rodatasize); 00511 rodata.address = (char *)text.address + textsize; 00512 00513 00514 PRINTF("bss base address: bss.address = 0x%08x\n", bss.address); 00515 PRINTF("data base address: data.address = 0x%08x\n", data.address); 00516 PRINTF("text base address: text.address = 0x%08x\n", text.address); 00517 PRINTF("rodata base address: rodata.address = 0x%08x\n", rodata.address); 00518 00519 00520 /* If we have text segment relocations, we process them. */ 00521 PRINTF("elfloader: relocate text\n"); 00522 if(textrelasize > 0) { 00523 ret = relocate_section(fd, 00524 textrelaoff, textrelasize, 00525 textoff, 00526 text.address, 00527 strs, 00528 strtaboff, 00529 symtaboff, symtabsize, using_relas); 00530 if(ret != ELFLOADER_OK) { 00531 return ret; 00532 } 00533 } 00534 00535 /* If we have any rodata segment relocations, we process them too. */ 00536 PRINTF("elfloader: relocate rodata\n"); 00537 if(rodatarelasize > 0) { 00538 ret = relocate_section(fd, 00539 rodatarelaoff, rodatarelasize, 00540 rodataoff, 00541 rodata.address, 00542 strs, 00543 strtaboff, 00544 symtaboff, symtabsize, using_relas); 00545 if(ret != ELFLOADER_OK) { 00546 PRINTF("elfloader: data failed\n"); 00547 return ret; 00548 } 00549 } 00550 00551 /* If we have any data segment relocations, we process them too. */ 00552 PRINTF("elfloader: relocate data\n"); 00553 if(datarelasize > 0) { 00554 ret = relocate_section(fd, 00555 datarelaoff, datarelasize, 00556 dataoff, 00557 data.address, 00558 strs, 00559 strtaboff, 00560 symtaboff, symtabsize, using_relas); 00561 if(ret != ELFLOADER_OK) { 00562 PRINTF("elfloader: data failed\n"); 00563 return ret; 00564 } 00565 } 00566 00567 /* Write text and rodata segment into flash and data segment into RAM. */ 00568 elfloader_arch_write_rom(fd, textoff, textsize, text.address); 00569 elfloader_arch_write_rom(fd, rodataoff, rodatasize, rodata.address); 00570 00571 memset(bss.address, 0, bsssize); 00572 seek_read(fd, dataoff, data.address, datasize); 00573 00574 PRINTF("elfloader: autostart search\n"); 00575 process = (struct process **) find_local_symbol(fd, "autostart_processes", symtaboff, symtabsize, strtaboff); 00576 if(process != NULL) { 00577 PRINTF("elfloader: autostart found\n"); 00578 elfloader_autostart_processes = process; 00579 return ELFLOADER_OK; 00580 } else { 00581 PRINTF("elfloader: no autostart\n"); 00582 process = (struct process **) find_program_processes(fd, symtaboff, symtabsize, strtaboff); 00583 if(process != NULL) { 00584 PRINTF("elfloader: FOUND PRG\n"); 00585 } 00586 return ELFLOADER_NO_STARTPOINT; 00587 } 00588 } 00589 /*---------------------------------------------------------------------------*/