diff options
Diffstat (limited to 'utils/sbtools/elftosb.c')
-rw-r--r-- | utils/sbtools/elftosb.c | 137 |
1 files changed, 96 insertions, 41 deletions
diff --git a/utils/sbtools/elftosb.c b/utils/sbtools/elftosb.c index 7a0c67e53b..601321d9c0 100644 --- a/utils/sbtools/elftosb.c +++ b/utils/sbtools/elftosb.c | |||
@@ -38,7 +38,10 @@ | |||
38 | #include "elf.h" | 38 | #include "elf.h" |
39 | #include "sb.h" | 39 | #include "sb.h" |
40 | 40 | ||
41 | #define bug(...) do { fprintf(stderr,"ERROR: "__VA_ARGS__); exit(1); } while(0) | 41 | #define _STR(a) #a |
42 | #define STR(a) _STR(a) | ||
43 | |||
44 | #define bug(...) do { fprintf(stderr,"["__FILE__":"STR(__LINE__)"]ERROR: "__VA_ARGS__); exit(1); } while(0) | ||
42 | #define bugp(a) do { perror("ERROR: "a); exit(1); } while(0) | 45 | #define bugp(a) do { perror("ERROR: "a); exit(1); } while(0) |
43 | 46 | ||
44 | bool g_debug = false; | 47 | bool g_debug = false; |
@@ -197,13 +200,14 @@ enum cmd_inst_type_t | |||
197 | CMD_LOAD_AT, /* load binary at */ | 200 | CMD_LOAD_AT, /* load binary at */ |
198 | CMD_CALL_AT, /* call at address */ | 201 | CMD_CALL_AT, /* call at address */ |
199 | CMD_JUMP_AT, /* jump at address */ | 202 | CMD_JUMP_AT, /* jump at address */ |
203 | CMD_MODE, /* change boot mode */ | ||
200 | }; | 204 | }; |
201 | 205 | ||
202 | struct cmd_inst_t | 206 | struct cmd_inst_t |
203 | { | 207 | { |
204 | enum cmd_inst_type_t type; | 208 | enum cmd_inst_type_t type; |
205 | char *identifier; | 209 | char *identifier; |
206 | uint32_t argument; // for jump, call | 210 | uint32_t argument; // for jump, call, mode |
207 | uint32_t addr; // for 'at' | 211 | uint32_t addr; // for 'at' |
208 | struct cmd_inst_t *next; | 212 | struct cmd_inst_t *next; |
209 | }; | 213 | }; |
@@ -253,18 +257,18 @@ static void __parse_string(char **ptr, char *end, void *user, void (*emit_fn)(vo | |||
253 | { | 257 | { |
254 | (*ptr)++; | 258 | (*ptr)++; |
255 | if(*ptr == end) | 259 | if(*ptr == end) |
256 | bug("Unfinished string"); | 260 | bug("Unfinished string\n"); |
257 | if(**ptr == '\\') emit_fn(user, '\\'); | 261 | if(**ptr == '\\') emit_fn(user, '\\'); |
258 | else if(**ptr == '\'') emit_fn(user, '\''); | 262 | else if(**ptr == '\'') emit_fn(user, '\''); |
259 | else if(**ptr == '\"') emit_fn(user, '\"'); | 263 | else if(**ptr == '\"') emit_fn(user, '\"'); |
260 | else bug("Unknown escape sequence \\%c", **ptr); | 264 | else bug("Unknown escape sequence \\%c\n", **ptr); |
261 | (*ptr)++; | 265 | (*ptr)++; |
262 | } | 266 | } |
263 | else | 267 | else |
264 | emit_fn(user, *(*ptr)++); | 268 | emit_fn(user, *(*ptr)++); |
265 | } | 269 | } |
266 | if(*ptr == end || **ptr != '"') | 270 | if(*ptr == end || **ptr != '"') |
267 | bug("unfinished string"); | 271 | bug("unfinished string\n"); |
268 | (*ptr)++; | 272 | (*ptr)++; |
269 | } | 273 | } |
270 | 274 | ||
@@ -315,9 +319,9 @@ static void parse_ascii_number(char **ptr, char *end, struct lexem_t *lexem) | |||
315 | break; | 319 | break; |
316 | } | 320 | } |
317 | if(*ptr == end || **ptr != '\'') | 321 | if(*ptr == end || **ptr != '\'') |
318 | bug("Unterminated ascii number literal"); | 322 | bug("Unterminated ascii number literal\n"); |
319 | if(len != 1 && len != 2 && len != 4) | 323 | if(len != 1 && len != 2 && len != 4) |
320 | bug("Invalid ascii number literal length: only 1, 2 or 4 are valid"); | 324 | bug("Invalid ascii number literal length: only 1, 2 or 4 are valid\n"); |
321 | /* skip ' */ | 325 | /* skip ' */ |
322 | (*ptr)++; | 326 | (*ptr)++; |
323 | lexem->type = LEX_NUMBER; | 327 | lexem->type = LEX_NUMBER; |
@@ -371,13 +375,30 @@ static void next_lexem(char **ptr, char *end, struct lexem_t *lexem) | |||
371 | (*ptr)++; | 375 | (*ptr)++; |
372 | continue; | 376 | continue; |
373 | } | 377 | } |
374 | /* skip comments */ | 378 | /* skip C++ style comments */ |
375 | if(**ptr == '/' && (*ptr) + 1 != end && (*ptr)[1] == '/') | 379 | if(**ptr == '/' && (*ptr) + 1 != end && (*ptr)[1] == '/') |
376 | { | 380 | { |
377 | while(*ptr != end && **ptr != '\n') | 381 | while(*ptr != end && **ptr != '\n') |
378 | (*ptr)++; | 382 | (*ptr)++; |
379 | continue; | 383 | continue; |
380 | } | 384 | } |
385 | /* skip C-style comments */ | ||
386 | if(**ptr == '/' && (*ptr) + 1 != end && (*ptr)[1] == '*') | ||
387 | { | ||
388 | (*ptr) += 2; | ||
389 | if(*ptr == end) | ||
390 | bug("invalid command file: unterminated comment"); | ||
391 | while(true) | ||
392 | { | ||
393 | if(**ptr == '*' && (*ptr) + 1 != end && (*ptr)[1] == '/') | ||
394 | { | ||
395 | (*ptr) += 2; | ||
396 | break; | ||
397 | } | ||
398 | (*ptr)++; | ||
399 | } | ||
400 | continue; | ||
401 | } | ||
381 | break; | 402 | break; |
382 | } | 403 | } |
383 | if(*ptr == end) ret_simple(LEX_EOF, 0); | 404 | if(*ptr == end) ret_simple(LEX_EOF, 0); |
@@ -455,10 +476,10 @@ static struct cmd_file_t *read_command_file(const char *file) | |||
455 | /* sources */ | 476 | /* sources */ |
456 | next(); | 477 | next(); |
457 | if(lexem.type != LEX_IDENTIFIER || strcmp(lexem.str, "sources") != 0) | 478 | if(lexem.type != LEX_IDENTIFIER || strcmp(lexem.str, "sources") != 0) |
458 | bug("invalid command file: 'sources' expected"); | 479 | bug("invalid command file: 'sources' expected\n"); |
459 | next(); | 480 | next(); |
460 | if(lexem.type != LEX_LBRACE) | 481 | if(lexem.type != LEX_LBRACE) |
461 | bug("invalid command file: '{' expected after 'sources'"); | 482 | bug("invalid command file: '{' expected after 'sources'\n"); |
462 | 483 | ||
463 | while(true) | 484 | while(true) |
464 | { | 485 | { |
@@ -469,20 +490,20 @@ static struct cmd_file_t *read_command_file(const char *file) | |||
469 | memset(src, 0, sizeof(struct cmd_source_t)); | 490 | memset(src, 0, sizeof(struct cmd_source_t)); |
470 | src->next = cmd_file->source_list; | 491 | src->next = cmd_file->source_list; |
471 | if(lexem.type != LEX_IDENTIFIER) | 492 | if(lexem.type != LEX_IDENTIFIER) |
472 | bug("invalid command file: identifier expected in sources"); | 493 | bug("invalid command file: identifier expected in sources\n"); |
473 | src->identifier = lexem.str; | 494 | src->identifier = lexem.str; |
474 | next(); | 495 | next(); |
475 | if(lexem.type != LEX_EQUAL) | 496 | if(lexem.type != LEX_EQUAL) |
476 | bug("invalid command file: '=' expected after identifier"); | 497 | bug("invalid command file: '=' expected after identifier\n"); |
477 | next(); | 498 | next(); |
478 | if(lexem.type != LEX_STRING) | 499 | if(lexem.type != LEX_STRING) |
479 | bug("invalid command file: string expected after '='"); | 500 | bug("invalid command file: string expected after '='\n"); |
480 | src->filename = lexem.str; | 501 | src->filename = lexem.str; |
481 | next(); | 502 | next(); |
482 | if(lexem.type != LEX_SEMICOLON) | 503 | if(lexem.type != LEX_SEMICOLON) |
483 | bug("invalid command file: ';' expected after string"); | 504 | bug("invalid command file: ';' expected after string\n"); |
484 | if(find_source_by_id(cmd_file, src->identifier) != NULL) | 505 | if(find_source_by_id(cmd_file, src->identifier) != NULL) |
485 | bug("invalid command file: duplicated source identifier"); | 506 | bug("invalid command file: duplicated source identifier\n"); |
486 | /* type filled later */ | 507 | /* type filled later */ |
487 | src->type = CMD_SRC_UNK; | 508 | src->type = CMD_SRC_UNK; |
488 | cmd_file->source_list = src; | 509 | cmd_file->source_list = src; |
@@ -499,10 +520,10 @@ static struct cmd_file_t *read_command_file(const char *file) | |||
499 | if(lexem.type == LEX_EOF) | 520 | if(lexem.type == LEX_EOF) |
500 | break; | 521 | break; |
501 | if(lexem.type != LEX_IDENTIFIER || strcmp(lexem.str, "section") != 0) | 522 | if(lexem.type != LEX_IDENTIFIER || strcmp(lexem.str, "section") != 0) |
502 | bug("invalid command file: 'section' expected"); | 523 | bug("invalid command file: 'section' expected\n"); |
503 | next(); | 524 | next(); |
504 | if(lexem.type != LEX_LPAREN) | 525 | if(lexem.type != LEX_LPAREN) |
505 | bug("invalid command file: '(' expected after 'section'"); | 526 | bug("invalid command file: '(' expected after 'section'\n"); |
506 | next(); | 527 | next(); |
507 | /* can be a number or a 4 character long string */ | 528 | /* can be a number or a 4 character long string */ |
508 | if(lexem.type == LEX_NUMBER) | 529 | if(lexem.type == LEX_NUMBER) |
@@ -510,14 +531,14 @@ static struct cmd_file_t *read_command_file(const char *file) | |||
510 | sec->identifier = lexem.num; | 531 | sec->identifier = lexem.num; |
511 | } | 532 | } |
512 | else | 533 | else |
513 | bug("invalid command file: number expected as section identifier"); | 534 | bug("invalid command file: number expected as section identifier\n"); |
514 | 535 | ||
515 | next(); | 536 | next(); |
516 | if(lexem.type != LEX_RPAREN) | 537 | if(lexem.type != LEX_RPAREN) |
517 | bug("invalid command file: ')' expected after section identifier"); | 538 | bug("invalid command file: ')' expected after section identifier\n"); |
518 | next(); | 539 | next(); |
519 | if(lexem.type != LEX_LBRACE) | 540 | if(lexem.type != LEX_LBRACE) |
520 | bug("invalid command file: '{' expected after section directive"); | 541 | bug("invalid command file: '{' expected after section directive\n"); |
521 | /* commands */ | 542 | /* commands */ |
522 | while(true) | 543 | while(true) |
523 | { | 544 | { |
@@ -527,24 +548,26 @@ static struct cmd_file_t *read_command_file(const char *file) | |||
527 | if(lexem.type == LEX_RBRACE) | 548 | if(lexem.type == LEX_RBRACE) |
528 | break; | 549 | break; |
529 | if(lexem.type != LEX_IDENTIFIER) | 550 | if(lexem.type != LEX_IDENTIFIER) |
530 | bug("invalid command file: instruction expected in section"); | 551 | bug("invalid command file: instruction expected in section\n"); |
531 | if(strcmp(lexem.str, "load") == 0) | 552 | if(strcmp(lexem.str, "load") == 0) |
532 | inst->type = CMD_LOAD; | 553 | inst->type = CMD_LOAD; |
533 | else if(strcmp(lexem.str, "call") == 0) | 554 | else if(strcmp(lexem.str, "call") == 0) |
534 | inst->type = CMD_CALL; | 555 | inst->type = CMD_CALL; |
535 | else if(strcmp(lexem.str, "jump") == 0) | 556 | else if(strcmp(lexem.str, "jump") == 0) |
536 | inst->type = CMD_JUMP; | 557 | inst->type = CMD_JUMP; |
558 | else if(strcmp(lexem.str, "mode") == 0) | ||
559 | inst->type = CMD_MODE; | ||
537 | else | 560 | else |
538 | bug("invalid command file: instruction expected in section"); | 561 | bug("invalid command file: instruction expected in section\n"); |
539 | next(); | 562 | next(); |
540 | 563 | ||
541 | if(inst->type == CMD_LOAD) | 564 | if(inst->type == CMD_LOAD) |
542 | { | 565 | { |
543 | if(lexem.type != LEX_IDENTIFIER) | 566 | if(lexem.type != LEX_IDENTIFIER) |
544 | bug("invalid command file: identifier expected after instruction"); | 567 | bug("invalid command file: identifier expected after instruction\n"); |
545 | inst->identifier = lexem.str; | 568 | inst->identifier = lexem.str; |
546 | if(find_source_by_id(cmd_file, inst->identifier) == NULL) | 569 | if(find_source_by_id(cmd_file, inst->identifier) == NULL) |
547 | bug("invalid command file: undefined reference to source '%s'", inst->identifier); | 570 | bug("invalid command file: undefined reference to source '%s'\n", inst->identifier); |
548 | next(); | 571 | next(); |
549 | if(lexem.type == LEX_RANGLE) | 572 | if(lexem.type == LEX_RANGLE) |
550 | { | 573 | { |
@@ -552,12 +575,12 @@ static struct cmd_file_t *read_command_file(const char *file) | |||
552 | inst->type = CMD_LOAD_AT; | 575 | inst->type = CMD_LOAD_AT; |
553 | next(); | 576 | next(); |
554 | if(lexem.type != LEX_NUMBER) | 577 | if(lexem.type != LEX_NUMBER) |
555 | bug("invalid command file: number expected for loading address"); | 578 | bug("invalid command file: number expected for loading address\n"); |
556 | inst->addr = lexem.num; | 579 | inst->addr = lexem.num; |
557 | next(); | 580 | next(); |
558 | } | 581 | } |
559 | if(lexem.type != LEX_SEMICOLON) | 582 | if(lexem.type != LEX_SEMICOLON) |
560 | bug("invalid command file: expected ';' after command"); | 583 | bug("invalid command file: expected ';' after command\n"); |
561 | } | 584 | } |
562 | else if(inst->type == CMD_CALL || inst->type == CMD_JUMP) | 585 | else if(inst->type == CMD_CALL || inst->type == CMD_JUMP) |
563 | { | 586 | { |
@@ -565,7 +588,7 @@ static struct cmd_file_t *read_command_file(const char *file) | |||
565 | { | 588 | { |
566 | inst->identifier = lexem.str; | 589 | inst->identifier = lexem.str; |
567 | if(find_source_by_id(cmd_file, inst->identifier) == NULL) | 590 | if(find_source_by_id(cmd_file, inst->identifier) == NULL) |
568 | bug("invalid command file: undefined reference to source '%s'", inst->identifier); | 591 | bug("invalid command file: undefined reference to source '%s'\n", inst->identifier); |
569 | next(); | 592 | next(); |
570 | } | 593 | } |
571 | else if(lexem.type == LEX_NUMBER) | 594 | else if(lexem.type == LEX_NUMBER) |
@@ -575,24 +598,33 @@ static struct cmd_file_t *read_command_file(const char *file) | |||
575 | next(); | 598 | next(); |
576 | } | 599 | } |
577 | else | 600 | else |
578 | bug("invalid command file: identifier or number expected after jump/load"); | 601 | bug("invalid command file: identifier or number expected after jump/load\n"); |
579 | 602 | ||
580 | if(lexem.type == LEX_LPAREN) | 603 | if(lexem.type == LEX_LPAREN) |
581 | { | 604 | { |
582 | next(); | 605 | next(); |
583 | if(lexem.type != LEX_NUMBER) | 606 | if(lexem.type != LEX_NUMBER) |
584 | bug("invalid command file: expected numeral expression after ("); | 607 | bug("invalid command file: expected numeral expression after (\n"); |
585 | inst->argument = lexem.num; | 608 | inst->argument = lexem.num; |
586 | next(); | 609 | next(); |
587 | if(lexem.type != LEX_RPAREN) | 610 | if(lexem.type != LEX_RPAREN) |
588 | bug("invalid command file: expected closing brace"); | 611 | bug("invalid command file: expected closing brace\n"); |
589 | next(); | 612 | next(); |
590 | } | 613 | } |
591 | if(lexem.type != LEX_SEMICOLON) | 614 | if(lexem.type != LEX_SEMICOLON) |
592 | bug("invalid command file: expected ';' after command"); | 615 | bug("invalid command file: expected ';' after command\n"); |
616 | } | ||
617 | else if(inst->type == CMD_MODE) | ||
618 | { | ||
619 | if(lexem.type != LEX_NUMBER) | ||
620 | bug("invalid command file: number expected after 'mode'\n"); | ||
621 | inst->argument = lexem.num; | ||
622 | next(); | ||
623 | if(lexem.type != LEX_SEMICOLON) | ||
624 | bug("invalid command file: expected ';' after command\n"); | ||
593 | } | 625 | } |
594 | else | 626 | else |
595 | bug("die"); | 627 | bug("die\n"); |
596 | if(end_list == NULL) | 628 | if(end_list == NULL) |
597 | { | 629 | { |
598 | sec->inst_list = inst; | 630 | sec->inst_list = inst; |
@@ -685,7 +717,7 @@ static void load_elf_by_id(struct cmd_file_t *cmd_file, const char *id) | |||
685 | if(src->type == CMD_SRC_ELF && src->loaded) | 717 | if(src->type == CMD_SRC_ELF && src->loaded) |
686 | return; | 718 | return; |
687 | if(src->type != CMD_SRC_UNK) | 719 | if(src->type != CMD_SRC_UNK) |
688 | bug("source '%s' seen both as elf and binary file", id); | 720 | bug("source '%s' seen both as elf and binary file\n", id); |
689 | src->type = CMD_SRC_ELF; | 721 | src->type = CMD_SRC_ELF; |
690 | int fd = open(src->filename, O_RDONLY); | 722 | int fd = open(src->filename, O_RDONLY); |
691 | if(fd < 0) | 723 | if(fd < 0) |
@@ -708,7 +740,7 @@ static void load_bin_by_id(struct cmd_file_t *cmd_file, const char *id) | |||
708 | if(src->type == CMD_SRC_BIN && src->loaded) | 740 | if(src->type == CMD_SRC_BIN && src->loaded) |
709 | return; | 741 | return; |
710 | if(src->type != CMD_SRC_UNK) | 742 | if(src->type != CMD_SRC_UNK) |
711 | bug("source '%s' seen both as elf and binary file", id); | 743 | bug("source '%s' seen both as elf and binary file\n", id); |
712 | src->type = CMD_SRC_BIN; | 744 | src->type = CMD_SRC_BIN; |
713 | int fd = open(src->filename, O_RDONLY); | 745 | int fd = open(src->filename, O_RDONLY); |
714 | if(fd < 0) | 746 | if(fd < 0) |
@@ -773,8 +805,12 @@ static struct sb_file_t *apply_cmd_file(struct cmd_file_t *cmd_file) | |||
773 | load_bin_by_id(cmd_file, cinst->identifier); | 805 | load_bin_by_id(cmd_file, cinst->identifier); |
774 | sec->nr_insts++; | 806 | sec->nr_insts++; |
775 | } | 807 | } |
808 | else if(cinst->type == CMD_MODE) | ||
809 | { | ||
810 | sec->nr_insts++; | ||
811 | } | ||
776 | else | 812 | else |
777 | bug("die"); | 813 | bug("die\n"); |
778 | 814 | ||
779 | cinst = cinst->next; | 815 | cinst = cinst->next; |
780 | } | 816 | } |
@@ -830,8 +866,13 @@ static struct sb_file_t *apply_cmd_file(struct cmd_file_t *cmd_file) | |||
830 | sec->insts[idx].data = bin->data; | 866 | sec->insts[idx].data = bin->data; |
831 | sec->insts[idx++].size = bin->size; | 867 | sec->insts[idx++].size = bin->size; |
832 | } | 868 | } |
869 | else if(cinst->type == CMD_MODE) | ||
870 | { | ||
871 | sec->insts[idx].inst = SB_INST_MODE; | ||
872 | sec->insts[idx++].addr = cinst->argument; | ||
873 | } | ||
833 | else | 874 | else |
834 | bug("die"); | 875 | bug("die\n"); |
835 | 876 | ||
836 | cinst = cinst->next; | 877 | cinst = cinst->next; |
837 | } | 878 | } |
@@ -909,6 +950,15 @@ static void compute_sb_offsets(struct sb_file_t *sb) | |||
909 | sb->image_size += (inst->size + inst->padding_size) / BLOCK_SIZE; | 950 | sb->image_size += (inst->size + inst->padding_size) / BLOCK_SIZE; |
910 | sec->sec_size += (inst->size + inst->padding_size) / BLOCK_SIZE; | 951 | sec->sec_size += (inst->size + inst->padding_size) / BLOCK_SIZE; |
911 | } | 952 | } |
953 | else if(inst->inst == SB_INST_MODE) | ||
954 | { | ||
955 | if(g_debug) | ||
956 | printf("MODE | mod=0x%08x", inst->addr); | ||
957 | sb->image_size += sizeof(struct sb_instruction_mode_t) / BLOCK_SIZE; | ||
958 | sec->sec_size += sizeof(struct sb_instruction_mode_t) / BLOCK_SIZE; | ||
959 | } | ||
960 | else | ||
961 | bug("die on inst %d\n", inst->inst); | ||
912 | } | 962 | } |
913 | } | 963 | } |
914 | /* final signature */ | 964 | /* final signature */ |
@@ -999,26 +1049,31 @@ static void produce_section_tag_cmd(struct sb_section_t *sec, | |||
999 | void produce_sb_instruction(struct sb_inst_t *inst, | 1049 | void produce_sb_instruction(struct sb_inst_t *inst, |
1000 | struct sb_instruction_common_t *cmd) | 1050 | struct sb_instruction_common_t *cmd) |
1001 | { | 1051 | { |
1002 | cmd->hdr.flags = 0; | 1052 | memset(cmd, 0, sizeof(struct sb_instruction_common_t)); |
1003 | cmd->hdr.opcode = inst->inst; | 1053 | cmd->hdr.opcode = inst->inst; |
1004 | cmd->addr = inst->addr; | ||
1005 | cmd->len = inst->size; | ||
1006 | switch(inst->inst) | 1054 | switch(inst->inst) |
1007 | { | 1055 | { |
1008 | case SB_INST_CALL: | 1056 | case SB_INST_CALL: |
1009 | case SB_INST_JUMP: | 1057 | case SB_INST_JUMP: |
1010 | cmd->len = 0; | 1058 | cmd->addr = inst->addr; |
1011 | cmd->data = inst->argument; | 1059 | cmd->data = inst->argument; |
1012 | break; | 1060 | break; |
1013 | case SB_INST_FILL: | 1061 | case SB_INST_FILL: |
1062 | cmd->addr = inst->addr; | ||
1063 | cmd->len = inst->size; | ||
1014 | cmd->data = inst->pattern; | 1064 | cmd->data = inst->pattern; |
1015 | break; | 1065 | break; |
1016 | case SB_INST_LOAD: | 1066 | case SB_INST_LOAD: |
1067 | cmd->addr = inst->addr; | ||
1068 | cmd->len = inst->size; | ||
1017 | cmd->data = crc_continue(crc(inst->data, inst->size), | 1069 | cmd->data = crc_continue(crc(inst->data, inst->size), |
1018 | inst->padding, inst->padding_size); | 1070 | inst->padding, inst->padding_size); |
1019 | break; | 1071 | break; |
1020 | default: | 1072 | case SB_INST_MODE: |
1073 | cmd->data = inst->addr; | ||
1021 | break; | 1074 | break; |
1075 | default: | ||
1076 | bug("die\n"); | ||
1022 | } | 1077 | } |
1023 | cmd->hdr.checksum = instruction_checksum(&cmd->hdr); | 1078 | cmd->hdr.checksum = instruction_checksum(&cmd->hdr); |
1024 | } | 1079 | } |