Mercurial > ecos-v3_0-branch
annotate packages/services/objloader/current/src/objloader.c @ 2243:6fd46febe457
Warnings, cosmetic fixes from Anthony Tonizzo
| author | gthomas |
|---|---|
| date | Wed, 28 Jun 2006 12:48:46 +0000 |
| parents | 8bf90d16b837 |
| children | 74dbf4c3f2e1 |
| rev | line source |
|---|---|
| 2011 | 1 /* ================================================================= |
| 2 * | |
| 3 * objelf.c | |
| 4 * | |
| 5 * An object loader for eCos | |
| 6 * | |
| 7 * ================================================================= | |
| 8 * ####ECOSGPLCOPYRIGHTBEGIN#### | |
| 9 * ------------------------------------------- | |
| 10 * This file is part of eCos, the Embedded Configurable Operating | |
| 11 * System. | |
| 12 * Copyright (C) 2005 eCosCentric Ltd. | |
| 13 * | |
| 14 * eCos is free software; you can redistribute it and/or modify it | |
| 15 * under the terms of the GNU General Public License as published by | |
| 16 * the Free Software Foundation; either version 2 or (at your option) | |
| 17 * any later version. | |
| 18 * | |
| 19 * eCos is distributed in the hope that it will be useful, but | |
| 20 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 22 * General Public License for more details. | |
| 23 * | |
| 24 * You should have received a copy of the GNU General Public License | |
| 25 * along with eCos; if not, write to the Free Software Foundation, | |
| 26 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | |
| 27 * | |
| 28 * As a special exception, if other files instantiate templates or | |
| 29 * use macros or inline functions from this file, or you compile this | |
| 30 * file and link it with other works to produce a work based on this | |
| 31 * file, this file does not by itself cause the resulting work to be | |
| 32 * covered by the GNU General Public License. However the source code | |
| 33 * for this file must still be made available in accordance with | |
| 34 * section (3) of the GNU General Public License. | |
| 35 * | |
| 36 * This exception does not invalidate any other reasons why a work | |
| 37 * based on this file might be covered by the GNU General Public | |
| 38 * License. | |
| 39 * | |
| 40 * ------------------------------------------- | |
| 41 * ####ECOSGPLCOPYRIGHTEND#### | |
| 42 * ================================================================= | |
| 43 * #####DESCRIPTIONBEGIN#### | |
| 44 * | |
| 2243 | 45 * Author(s): Anthony Tonizzo (atonizzo@gmail.com) |
| 2011 | 46 * Contributors: nickg@ecoscentric.com |
| 47 * Date: 2005-05-13 | |
| 48 * Purpose: | |
| 49 * Description: | |
| 50 * | |
| 51 * ####DESCRIPTIONEND#### | |
| 52 * | |
| 53 * ================================================================= | |
| 54 */ | |
| 55 | |
| 56 #include <cyg/infra/diag.h> // For diagnostic printing. | |
| 57 #include <cyg/infra/cyg_ass.h> | |
| 58 #include <stdlib.h> | |
| 59 #include <string.h> | |
| 60 | |
| 61 #include <pkgconf/objloader.h> | |
|
2013
8bf90d16b837
* include/objelf.h: Include hal_tables.h otherwise we get strange
asl
parents:
2011
diff
changeset
|
62 |
| 2011 | 63 #include <cyg/objloader/elf.h> |
| 64 #include <cyg/objloader/objelf.h> | |
| 65 #include <cyg/objloader/loader_fs.h> | |
| 66 | |
| 2243 | 67 char *cyg_ldr_last_error; |
| 2011 | 68 |
| 2243 | 69 void *cyg_ldr_malloc(size_t) CYGBLD_ATTRIB_WEAK; |
| 2011 | 70 void |
| 2243 | 71 *cyg_ldr_malloc(size_t s) |
| 2011 | 72 { |
| 2243 | 73 return malloc(s); |
| 2011 | 74 } |
| 75 | |
| 2243 | 76 void cyg_ldr_free(void *) CYGBLD_ATTRIB_WEAK; |
| 2011 | 77 void |
| 2243 | 78 cyg_ldr_free(void *s) |
| 2011 | 79 { |
| 2243 | 80 free(s); |
| 2011 | 81 } |
| 82 | |
| 83 void | |
| 2243 | 84 cyg_ldr_delete_elf_section(PELF_OBJECT p, cyg_uint32 idx) |
| 2011 | 85 { |
| 2243 | 86 cyg_ldr_free(p->sections[idx]); |
| 2011 | 87 p->sections[idx] = 0; |
| 88 } | |
| 89 | |
| 90 // Frees all the memory allocated for a particular ELF object. Also calls | |
| 91 // the close() function to close files or sockets, and finally frees up | |
| 92 // the ELF object altogether. | |
| 93 static void | |
| 2243 | 94 cyg_ldr_free_elf_object(PELF_OBJECT p) |
| 2011 | 95 { |
| 96 cyg_int32 i; | |
| 97 | |
| 2243 | 98 for (i = 0; i < p->p_elfhdr->e_shnum + 1; i++) |
| 99 if (p->sections[i]) | |
| 100 cyg_ldr_delete_elf_section(p, i); | |
| 2011 | 101 |
| 2243 | 102 if (p->sections != 0) |
| 103 cyg_ldr_free(p->sections); | |
| 2011 | 104 |
| 2243 | 105 if (p->p_sechdr != 0) |
| 106 cyg_ldr_free(p->p_sechdr); | |
| 2011 | 107 |
| 2243 | 108 if (p->p_elfhdr != 0) |
| 109 cyg_ldr_free(p->p_elfhdr); | |
| 2011 | 110 |
| 2243 | 111 p->close(p); |
| 112 cyg_ldr_free(p); | |
| 2011 | 113 } |
| 114 | |
| 115 static cyg_uint32 | |
| 2243 | 116 cyg_ldr_find_common_size(PELF_OBJECT p) |
| 2011 | 117 { |
| 2243 | 118 cyg_int32 i, common_size = 0; |
| 2011 | 119 Elf32_Sym *p_symtab = (Elf32_Sym*)p->sections[p->hdrndx_symtab]; |
| 120 | |
| 121 // Total number of entries in the symbol table. | |
| 2243 | 122 int symtab_entries = p->p_sechdr[p->hdrndx_symtab].sh_size / |
| 2011 | 123 p->p_sechdr[p->hdrndx_symtab].sh_entsize; |
| 2243 | 124 for (i = 1; i < symtab_entries; i++) |
| 125 if (p_symtab[i].st_shndx == SHN_COMMON) | |
| 2011 | 126 { |
| 127 // In the case of an SHN_COMMON symbol the st_value field holds | |
| 128 // alignment constraints. | |
| 129 cyg_uint32 boundary = p_symtab[i].st_value - 1; | |
| 130 | |
| 131 // Calculate the next byte boundary. | |
| 2243 | 132 common_size = (common_size + boundary) & ~boundary; |
| 2011 | 133 common_size += p_symtab[i].st_size; |
| 134 } | |
| 135 | |
| 136 #if CYGPKG_SERVICES_OBJLOADER_DEBUG_LEVEL > 0 | |
| 2243 | 137 diag_printf("common_size = %d\n", common_size); |
| 2011 | 138 #endif |
| 139 return common_size; | |
| 140 } | |
| 141 | |
| 142 // Allocates memory and loads the contents of a specific ELF section. | |
| 143 // Returns the address of the newly allocated memory, of 0 for any error. | |
| 2243 | 144 cyg_uint32 |
| 145 *cyg_ldr_load_elf_section(PELF_OBJECT p, cyg_uint32 idx) | |
| 2011 | 146 { |
| 2243 | 147 cyg_uint32 *addr = (cyg_uint32 *)cyg_ldr_malloc(p->p_sechdr[idx].sh_size); |
| 148 CYG_ASSERT(addr != 0, "Cannot malloc() section"); | |
| 149 if (addr == 0) | |
| 2011 | 150 { |
| 151 cyg_ldr_last_error = "ERROR IN MALLOC"; | |
| 152 return (void*)0; | |
| 153 } | |
| 2243 | 154 p->seek(p, p->p_sechdr[idx].sh_offset); |
| 155 p->read(p, sizeof(char), p->p_sechdr[idx].sh_size, addr); | |
| 2011 | 156 return addr; |
| 157 } | |
| 158 | |
| 159 // Returns the starting address of a section. If the section is not already | |
| 160 // loaded in memory, area for it will be allocated and the section will be | |
| 161 // loaded. | |
| 2243 | 162 cyg_uint32 |
| 163 *cyg_ldr_section_address(PELF_OBJECT p, cyg_uint32 idx) | |
| 2011 | 164 { |
| 2243 | 165 if (p->sections[idx] == 0) |
| 166 p->sections[idx] = cyg_ldr_load_elf_section(p, idx); | |
| 2011 | 167 |
| 168 return p->sections[idx]; | |
| 169 } | |
| 170 | |
| 171 void | |
| 2243 | 172 *cyg_ldr_find_symbol(void* handle, char* sym_name) |
| 2011 | 173 { |
| 174 PELF_OBJECT p = (PELF_OBJECT)handle; | |
| 2243 | 175 int i; |
| 176 char *p_strtab = (char*)p->sections[p->hdrndx_strtab]; | |
| 2011 | 177 Elf32_Sym *p_symtab = (Elf32_Sym*)p->sections[p->hdrndx_symtab]; |
| 2243 | 178 |
| 179 int symtab_entries = p->p_sechdr[p->hdrndx_symtab].sh_size / | |
| 2011 | 180 p->p_sechdr[p->hdrndx_symtab].sh_entsize; |
| 181 | |
| 2243 | 182 for (i = 0; i < symtab_entries; i++) |
| 2011 | 183 { |
| 2243 | 184 char* tmp2 = p_strtab + p_symtab[i].st_name; |
| 185 if (!strcmp(tmp2, sym_name)) | |
| 186 return cyg_ldr_symbol_address(p, i); | |
| 2011 | 187 } |
| 188 | |
| 189 // Symbol not found. | |
| 190 cyg_ldr_last_error = "SYMBOL NOT FOUND"; | |
| 191 return 0; | |
| 192 } | |
| 193 | |
| 2243 | 194 static char |
| 195 *cyg_ldr_sanity_check(PELF_OBJECT p) | |
| 2011 | 196 { |
| 2243 | 197 if ((p->p_elfhdr->e_ident[EI_MAG0] != ELFMAG0) || |
| 198 (p->p_elfhdr->e_ident[EI_MAG1] != ELFMAG1) || | |
| 199 (p->p_elfhdr->e_ident[EI_MAG2] != ELFMAG2 ) || | |
| 200 (p->p_elfhdr->e_ident[EI_MAG3] != ELFMAG3) || | |
| 201 (p->p_elfhdr->e_ident[EI_CLASS] != ELFCLASS32)) | |
| 2011 | 202 return "INVALID ELF HEADER"; |
| 203 | |
| 204 // We only work with relocatable files. No dynamic linking. | |
| 2243 | 205 if (p->p_elfhdr->e_type != ET_REL) |
| 2011 | 206 return "NOT RELOCATABLE"; |
| 207 | |
| 208 // These #defines are sitting in the hal. | |
| 2243 | 209 if (p->p_elfhdr->e_machine != ELF_ARCH_MACHINE_TYPE) |
| 2011 | 210 return "INVALID ARCHITECTURE"; |
| 211 | |
| 2243 | 212 if (p->p_elfhdr->e_ident[EI_DATA] != ELF_ARCH_ENDIANNESS) |
| 2011 | 213 return "INVALID ENDIAN"; |
| 214 return 0; } | |
| 215 | |
| 216 // Load only the ELF header and the sections header. These are the only | |
| 217 // sections loaded during library initialization. All the other sections | |
| 218 // will be loaded on demand when needed during the relocation process and, | |
| 219 // when possible, dumped after use. | |
| 220 static cyg_int32 | |
| 2243 | 221 cyg_ldr_load_sections(PELF_OBJECT p) |
| 2011 | 222 { |
| 2243 | 223 char *error_string; |
| 2011 | 224 cyg_int32 idx; |
| 225 | |
| 226 // Load the ELF header. | |
| 2243 | 227 p->p_elfhdr = (Elf32_Ehdr*)cyg_ldr_malloc(sizeof(Elf32_Ehdr)); |
| 228 CYG_ASSERT(p->p_elfhdr != 0, "Cannot malloc() p->p_elfhdr"); | |
| 229 if (p->p_elfhdr == 0) | |
| 2011 | 230 return -1; |
| 2243 | 231 p->seek(p, 0); |
| 232 p->read(p, sizeof(char), sizeof(Elf32_Ehdr), p->p_elfhdr ); | |
| 233 error_string = cyg_ldr_sanity_check(p); | |
| 234 if (error_string != 0) | |
| 2011 | 235 { |
| 236 cyg_ldr_last_error = "ERROR IN ELF HEADER"; | |
| 237 return -1; | |
| 238 } | |
| 239 | |
| 240 // Allocate an array that can hold an address to all the section of this | |
| 241 // library. This is not strictly optimal, since some sections do not | |
| 242 // need to be loaded all the time. Allocate an extra pointer for the | |
| 243 // COMMON area. | |
| 2243 | 244 p->sections = cyg_ldr_malloc((p->p_elfhdr->e_shnum + 1) * |
| 245 sizeof(cyg_uint32)); | |
| 246 CYG_ASSERT(p->sections != 0, "Cannot malloc() p->sections"); | |
| 247 if (p->sections == 0) | |
| 2011 | 248 { |
| 249 cyg_ldr_last_error = "ERROR IN MALLOC"; | |
| 250 return -1; | |
| 251 } | |
| 2243 | 252 memset(p->sections, 0, (p->p_elfhdr->e_shnum + 1) * |
| 253 sizeof(cyg_uint32)); | |
| 254 | |
| 255 // Now that the header is loaded, load the sections header. | |
| 256 p->p_sechdr = (Elf32_Shdr*)cyg_ldr_malloc( | |
| 257 p->p_elfhdr->e_shnum * p->p_elfhdr->e_shentsize); | |
| 258 CYG_ASSERT(p->p_sechdr != 0, "Cannot malloc() p->p_sechdr"); | |
| 259 if (p->p_sechdr == 0) | |
| 260 { | |
| 261 cyg_ldr_last_error = "ERROR IN MALLOC"; | |
| 262 return -1; | |
| 263 } | |
| 264 p->seek(p, p->p_elfhdr->e_shoff); | |
| 265 p->read(p, p->p_elfhdr->e_shentsize, p->p_elfhdr->e_shnum, p->p_sechdr); | |
| 2011 | 266 |
| 267 // Load the section header string table. This is a byte oriented table, | |
| 268 // so alignment is not an issue. | |
| 269 idx = p->p_elfhdr->e_shstrndx; | |
| 2243 | 270 p->sections[idx] = cyg_ldr_load_elf_section(p, idx); |
| 2011 | 271 return 0; |
| 272 } | |
| 273 | |
| 274 PELF_OBJECT | |
| 2243 | 275 cyg_ldr_open_library(CYG_ADDRWORD ptr, cyg_int32 mode) |
| 2011 | 276 { |
| 277 int (*fn)(void); | |
| 2243 | 278 int i; |
| 2011 | 279 |
| 280 // In the future there might be a switch() (against 'mode') that calls an | |
| 281 // open function other than cyg_ldr_open_library_fs(). These function | |
| 282 // fetch and open a library using ftp, http or libraries that are already | |
| 283 // in ROM. | |
| 2243 | 284 PELF_OBJECT e_obj = cyg_ldr_open_library_fs((char*)ptr); |
| 285 if (e_obj == 0) | |
| 2011 | 286 return 0; |
| 2243 | 287 int rc = cyg_ldr_load_sections(e_obj); |
| 288 if (rc != 0) | |
| 2011 | 289 { |
| 2243 | 290 cyg_ldr_free_elf_object(e_obj); |
| 2011 | 291 return 0; |
| 292 } | |
| 293 | |
| 294 // Find the section index for the .shstrtab section. The names of the | |
| 295 // sections are held here, and are the only way to identify them. | |
| 2243 | 296 char *p_shstrtab = (char*)cyg_ldr_section_address(e_obj, |
| 297 e_obj->p_elfhdr->e_shstrndx); | |
| 298 if (p_shstrtab == 0) | |
| 2011 | 299 { |
| 2243 | 300 cyg_ldr_free_elf_object(e_obj); |
| 2011 | 301 return 0; |
| 302 } | |
| 303 | |
| 304 // .symtab section and .strtab. We have to go through the section names | |
| 305 // to find where they are. | |
| 2243 | 306 for (i = 1; i < e_obj->p_elfhdr->e_shnum; i++) |
| 2011 | 307 { |
| 308 // Now look for the index of .symtab. These are the symbols needed for | |
| 309 // the symbol retrieval as well as relocation. | |
| 2243 | 310 if (!strcmp(p_shstrtab + e_obj->p_sechdr[i].sh_name, ELF_STRING_symtab)) |
| 2011 | 311 { |
| 312 e_obj->hdrndx_symtab = i; | |
| 2243 | 313 e_obj->sections[i] = cyg_ldr_load_elf_section(e_obj, i); |
| 314 if (e_obj->sections[i] == 0) | |
| 2011 | 315 { |
| 2243 | 316 cyg_ldr_free_elf_object(e_obj); |
| 2011 | 317 return 0; |
| 318 } | |
| 319 } | |
| 320 | |
| 321 // Load the table with the names of all the symbols. We need this | |
| 322 // to compare the name of external references symbols against the | |
| 323 // names in the in the CYG_HAL_TABLE provided by the user. | |
| 2243 | 324 if (!strcmp(p_shstrtab + e_obj->p_sechdr[i].sh_name, ELF_STRING_strtab)) |
| 2011 | 325 { |
| 326 e_obj->hdrndx_strtab = i; | |
| 2243 | 327 e_obj->sections[i] = cyg_ldr_load_elf_section(e_obj, i); |
| 328 if (e_obj->sections[i] == 0) | |
| 2011 | 329 { |
| 2243 | 330 cyg_ldr_free_elf_object(e_obj); |
| 2011 | 331 return 0; |
| 332 } | |
| 333 } | |
| 334 } | |
| 335 | |
| 2243 | 336 CYG_ASSERT(e_obj->hdrndx_symtab != 0, "No symtab index found"); |
| 337 CYG_ASSERT(e_obj->hdrndx_strtab != 0, "No strtab index found"); | |
| 2011 | 338 |
| 339 // Now look for symbols in the COMMON area. The COMMON symbols are a | |
| 340 // special case, because the area they reside in must be sized up | |
| 341 // and allocated separately from the other sections, which appear in | |
| 342 // the sections header and can be read out of the library itself. | |
| 343 // Extra room in the 'sections' array has already been allocated to hold | |
| 344 // the pointer to the commons area. | |
| 2243 | 345 cyg_uint32 common_size = cyg_ldr_find_common_size(e_obj); |
| 346 if (common_size != 0) | |
| 2011 | 347 { |
| 348 cyg_uint32 com_shndx = e_obj->p_elfhdr->e_shnum; | |
| 349 cyg_int32 com_offset = 0; | |
| 350 | |
| 2243 | 351 e_obj->sections[com_shndx] = (cyg_uint32*)cyg_ldr_malloc(common_size); |
| 352 CYG_ASSERT(e_obj->sections[com_shndx] != 0, | |
| 353 "Cannot malloc() the COMMONS"); | |
| 2011 | 354 |
| 2243 | 355 if (e_obj->sections[com_shndx] == 0) |
| 2011 | 356 { |
| 2243 | 357 cyg_ldr_free_elf_object(e_obj); |
| 2011 | 358 return 0; |
| 359 } | |
| 360 | |
| 361 // Now find all the symbols in the SHN_COMMON area and make | |
| 362 // them point to the newly allocated COM area. | |
| 2243 | 363 int symtab_entries = e_obj->p_sechdr[e_obj->hdrndx_symtab].sh_size / |
| 2011 | 364 e_obj->p_sechdr[e_obj->hdrndx_symtab].sh_entsize; |
| 2243 | 365 Elf32_Sym *p_symtab = (Elf32_Sym*)e_obj->sections[e_obj->hdrndx_symtab]; |
| 2011 | 366 |
| 367 #if CYGPKG_SERVICES_OBJLOADER_DEBUG_LEVEL > 1 | |
| 2243 | 368 diag_printf("Num Value Size Ndx Name\n"); |
| 2011 | 369 #endif |
| 2243 | 370 for (i = 1; i < symtab_entries; i++) |
| 2011 | 371 { |
| 372 #if CYGPKG_SERVICES_OBJLOADER_DEBUG_LEVEL > 1 | |
| 2243 | 373 cyg_uint8 *p_strtab = (cyg_uint8*)cyg_ldr_section_address(e_obj, |
| 374 e_obj->hdrndx_strtab); | |
| 2011 | 375 #endif |
| 2243 | 376 if (p_symtab[i].st_shndx == SHN_COMMON) |
| 2011 | 377 { |
| 378 cyg_uint32 boundary = p_symtab[i].st_value - 1; | |
| 379 // Calculate the next byte boundary. | |
| 2243 | 380 com_offset = (com_offset + boundary) & ~boundary; |
| 2011 | 381 p_symtab[i].st_shndx = com_shndx; |
| 382 p_symtab[i].st_value = com_offset; | |
| 383 com_offset += p_symtab[i].st_size; | |
| 384 } | |
| 385 | |
| 386 #if CYGPKG_SERVICES_OBJLOADER_DEBUG_LEVEL > 1 | |
| 2243 | 387 diag_printf("%03d %08X %04X %03d %s\n", |
| 2011 | 388 i, |
| 389 p_symtab[i].st_value, | |
| 390 p_symtab[i].st_size, | |
| 391 p_symtab[i].st_shndx, | |
| 2243 | 392 p_strtab + p_symtab[i].st_name); |
| 2011 | 393 #endif |
| 394 } | |
| 395 } | |
| 396 | |
| 397 #if CYGPKG_SERVICES_OBJLOADER_DEBUG_LEVEL > 0 | |
| 2243 | 398 cyg_ldr_print_section_data(e_obj); |
| 2011 | 399 #if CYGPKG_SERVICES_OBJLOADER_DEBUG_LEVEL > 1 |
| 2243 | 400 cyg_ldr_print_symbol_names(e_obj); |
| 2011 | 401 #endif |
| 402 #endif | |
| 403 | |
| 2243 | 404 for (i = 1; i < e_obj->p_elfhdr->e_shnum; i++) |
| 2011 | 405 { |
| 406 // Find all the '.rel' or '.rela' sections and relocate them. | |
| 2243 | 407 if ((e_obj->p_sechdr[i].sh_type == SHT_REL) || |
| 408 (e_obj->p_sechdr[i].sh_type == SHT_RELA)) | |
| 2011 | 409 { |
| 410 // Load and relocate the section. | |
| 2243 | 411 rc = cyg_ldr_relocate_section(e_obj, i); |
| 412 if (rc < 0) | |
| 2011 | 413 { |
| 414 #if CYGPKG_SERVICES_OBJLOADER_DEBUG_LEVEL > 0 | |
| 2243 | 415 ELFDEBUG("Relocation unsuccessful\n"); |
| 2011 | 416 #endif |
| 2243 | 417 cyg_ldr_free_elf_object(e_obj); |
| 2011 | 418 return 0; |
| 419 } | |
| 420 } | |
| 421 } | |
| 422 | |
| 423 // Synch up the caches before calling any function in the library. | |
| 424 cyg_ldr_flush_cache(); | |
| 425 | |
| 426 // Run the library initialization code. | |
| 2243 | 427 fn = cyg_ldr_find_symbol(e_obj, "library_open"); |
| 428 if (fn != 0) | |
| 2011 | 429 fn(); |
| 430 return ((void*)e_obj); | |
| 431 } | |
| 432 | |
| 2243 | 433 char |
| 434 *cyg_ldr_error(void) | |
| 2011 | 435 { |
| 2243 | 436 char* p = cyg_ldr_last_error; |
| 2011 | 437 cyg_ldr_last_error = NULL; |
| 438 return p; | |
| 439 } | |
| 440 | |
| 2243 | 441 void cyg_ldr_close_library(void* handle) |
| 2011 | 442 { |
| 443 void (*fn)(void); | |
| 444 | |
| 445 PELF_OBJECT p = (PELF_OBJECT)handle; | |
| 2243 | 446 fn = cyg_ldr_find_symbol(p, "library_close"); |
| 447 if (fn != 0) | |
| 2011 | 448 fn(); |
| 449 | |
| 2243 | 450 cyg_ldr_free_elf_object(p); |
| 2011 | 451 p = 0; |
| 452 } | |
| 453 |
