1#!/usr/bin/env python3 2# encoding=utf-8 3# ============================================================================ 4# @brief Mem Script file 5 6# Copyright (c) 2020 HiSilicon (Shanghai) Technologies CO., LIMITED. 7# Licensed under the Apache License, Version 2.0 (the "License"); 8# you may not use this file except in compliance with the License. 9# You may obtain a copy of the License at 10# 11# http://www.apache.org/licenses/LICENSE-2.0 12# 13# Unless required by applicable law or agreed to in writing, software 14# distributed under the License is distributed on an "AS IS" BASIS, 15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16# See the License for the specific language governing permissions and 17# limitations under the License. 18# ============================================================================ 19import os 20import sys 21import errno 22import re 23 24# Security Core - Test Suite 25# -------------------------- 26# 27# Section Start Addr Size Length 28# ------- ---------- ------ ------- 29# 30# Flash 0x10000000 0x31f80 0x13360 = 78688 31# startup 0x10000000 0xa0 32# .flash_version 0x100000c0 0x28 33# .text 0x10000100 0x118b4 34# .ARM.exidx 0x100119b4 0xf08 35# .ARM.extab 0x100128bc 0x924 36# .ram_text 0x100131e0 0x88 37# .data 0x10013268 0xf8 38# 39# SRAM 0x38000000 0x2800 0x27A0 = 10144 40# .stacks 0x38000000 0x400 41# .ramtext 0x38000400 0x88 load address 0x100131e0 42# .data 0x38000488 0xf8 load address 0x10013268 43# .bss 0x38000580 0x1e34 44# .preserve 0x380023b4 0x9c 45# .heap 0x38002450 0x0 46# .ipc_mailbox 0x38002700 0xa0 47 48flag_names = [ 49 'CONTENTS', 50 'ALLOC', 51 'LOAD', 52 'READONLY', 53 'CODE', 54 'DATA', 55 'DEBUGGING'] 56 57 58class Section: 59 def __init__(self): 60 self.name = '' 61 self.size = 0 62 self.vma = 0 63 self.lma = 0 64 self.flags = '' 65 self.exclude = False 66 67 def get_name(self): 68 return self.name 69 70 def get_size(self): 71 return self.size 72 73 def is_loaded(self): 74 if(not self.exclude): 75 if(self.lma != self.vma): 76 if('LOAD' in self.flags): 77 return True 78 return False 79 80 def get_load_section(self): 81 load_section = Section() 82 load_section.name = self.name 83 load_section.size = self.size 84 load_section.vma = self.lma 85 load_section.lma = self.lma 86 load_section.flags = self.flags 87 return load_section 88 89 def display(self): 90 size_str = "%d" % self.size 91 if(self.vma == self.lma): 92 print( 93 " %-16s 0x%08X %10s %8s " % 94 (self.name, self.vma, "", size_str)) 95 else: 96 print(" %-16s 0x%08X %10s %8s load address 0x%X" % 97 (self.name, self.vma, "", size_str, self.lma)) 98 99 def display_raw(self): 100 print("<%-16s 0x%08X 0x%08X 0x%08X %s>" % 101 (self.name, self.vma, self.lma, self.size, self.flags)) 102 103 def get_all_size(self, size_list): 104 temp = "{0}:{1}:{2}".format(self.name, self.vma, self.size) 105 size_list.append(temp) 106 107 108class Region: 109 def __init__(self, name, origin, length): 110 self.name = name 111 self.origin = origin 112 self.length = length 113 self.sections = [] 114 115 def get_name(self): 116 return self.name 117 118 # Get used_size or region length, if it does not contain any sections 119 def get_size(self): 120 used_size = self.used_size() 121 if(used_size > 0): 122 return used_size 123 return self.length 124 125 def add_section(self, section): 126 if(not section.exclude): 127 if((section.vma >= self.origin) and 128 (section.vma < (self.origin + self.length))): 129 self.sections.append(section) 130 return True 131 return False 132 133 def find_section(self, name): 134 if name in self.name: 135 return self 136 137 for section in self.sections: 138 if name in section.name: 139 return Region(section.name, section.vma, section.size) 140 return None 141 142 # Calculate region size used based on contained sections 143 def used_size(self): 144 # Begin with start at end of memory region... 145 start = self.origin + self.length 146 end = self.origin # and end at beginning 147 for section in self.sections: 148 if(section.vma < start): 149 start = section.vma 150 if((section.vma + section.size) > end): 151 end = (section.vma + section.size) 152 if(start < end): 153 return end - start # Beginning of first section to end of last 154 else: 155 return 0 # No sections cotained by this memory region 156 157 def display(self): 158 used_size = self.used_size() 159 if(used_size > 0): 160 used_size_str = "0x%X" % used_size 161 length_str = "0x%X" % self.length 162 print(" ") 163 print(" %-18s 0x%08X %10s %8s = %d" % 164 (self.name, self.origin, length_str, used_size_str, 165 used_size)) 166 for section in self.sections: 167 section.display() 168 169 def get_section_size(self, section_name): 170 size_list = [] 171 for section in self.sections: 172 section.get_all_size(size_list) 173 for val in size_list: 174 if section_name in val: 175 return int(val.split(":")[2]) 176 return 0 177 178 def get_section_used_size(self, section_name): 179 size_list = [] 180 for section in self.sections: 181 section.get_all_size(size_list) 182 for i, x in enumerate(size_list): 183 if section_name in x and i != (len(size_list) - 1): 184 return x.split(":")[-1] 185 return 0 186 187 188class Memory: 189 def __init__(self): 190 self.regions = [] 191 self.bth_list = [0] * 21 192 self.btc_list = [0] * 21 193 self.plt_list = [0] * 21 194 self.all_size = 0 195 self.filename = "" 196 self.chipname = "" 197 self.all_code_rodata_list = 0 198 self.all_data_list = 0 199 self.all_bss_list = 0 200 self.all_memory_size = 985088 201 202 def add_region(self, region): 203 self.regions.append(region) 204 205 def add_section(self, section): 206 for region in self.regions: 207 if(region.add_section(section)): 208 return True 209 return False 210 211 def find_section(self, name): 212 for region in self.regions: 213 section = region.find_section(name) 214 if section is not None: 215 return section 216 return None 217 218 def display(self): 219 print(" %-18s %10s %10s %8s" % 220 ('Section', 'Start Addr', 'Size', 'Used')) 221 print(" %-18s %10s %10s %8s" % 222 ('------------------', '----------', '----------', '--------')) 223 for region in self.regions: 224 region.display() 225 226 def summary_display(self, lst_file_name): 227 print(" ") 228 print(" %-18s %10s %10s %8s %8s" % 229 ('Section', 'Total', 'BTH', 'BTC', 'PLT')) 230 print(" %-18s %10s %10s %8s %8s" % 231 ('------------------', '----------', '----------', '--------', 232 '--------')) 233 print(" ") 234 self.memory_display(lst_file_name) 235 print(" %-18s %10s %10s %8s" % 236 ('Section', 'Start Addr', 'Size', 'Used')) 237 print(" %-18s %10s %10s %8s" % 238 ('------------------', '----------', '----------', '--------')) 239 for region in self.regions: 240 region.display() 241 242 def get_region_section_size(self, region_name, section_name): 243 for region in self.regions: 244 if region_name in region.name: 245 return region.get_section_size(section_name) 246 return 0 247 248 def get_region_section_used_size(self, region_name, section_name): 249 for region in self.regions: 250 if region_name in region.name: 251 return region.get_section_used_size(section_name) 252 253 def set_init_param(self, name, index, value): 254 if name in "btc_list": 255 self.btc_list[index] = value 256 elif name in "plt_list": 257 self.plt_list[index] = value 258 elif name in "bth_list": 259 self.bth_list[index] = value 260 261 262# Used to aid parsing lines of text from a Lst file 263class SectionLine: 264 def __init__(self, line): 265 self.line = line 266 267 def update(self, line): 268 self.line = line 269 270 def append(self, line): 271 self.line += line 272 273 def length(self): 274 return len(self.line) 275 276 def find_space(self, pos): 277 length = len(self.line) 278 while((pos < length) and not self.line[pos].isspace()): 279 pos = pos + 1 280 if(pos < length): 281 return pos 282 return length 283 284 def find_nonspace(self, pos): 285 length = len(self.line) 286 while((pos < length) and self.line[pos].isspace()): 287 pos = pos + 1 288 if(pos < length): 289 return pos 290 return length 291 292 def get_word(self, index): 293 start = self.find_nonspace(0) 294 end = self.find_space(start) 295 while(index > 0): 296 index = index - 1 297 start = self.find_nonspace(end) 298 end = self.find_space(start) 299 return self.line[start:end] 300 301 def contains(self, line): 302 if(line in self.line): 303 return True 304 return False 305 306 307class LstFile: 308 def __init__(self, fp_filename, filename, lds_file, chip_name, chip_version): 309 self.file = fp_filename 310 self.line = SectionLine("") 311 self.chip_name = chip_name 312 self.memory = self.create_memory_map(lds_file) 313 self.memory.chipname = chip_name 314 self.filename = filename 315 self.chip_version = chip_version 316 317 def __del__(self): 318 self.file.close() 319 320 def valid_section_line(self): 321 if(self.line.length() > 0): 322 if(self.line.get_word(0).isdigit()): 323 return True 324 return False 325 326 def get_next_section(self): 327 if(self.read_section_line()): 328 if(self.valid_section_line()): 329 section = self.parse_section_line() 330 if(section is not None): 331 # Move this knowledge into Section? 332 # Not a physical section 333 if(('READONLY' in section.flags) and 334 ('CODE' not in section.flags) and 335 ('DATA' not in section.flags)): 336 section.exclude = True 337 # Debugging sections do not appear in binary 338 if('DEBUGGING' in section.flags): 339 section.exclude = True 340 # Section might have a phantom load address 341 if((section.lma != section.vma) and 342 ('LOAD' not in section.flags)): 343 section.lma = section.vma 344 return section 345 return None 346 347 def get_first_section(self): 348 self.file.seek(0) 349 if(self.find_file_sections()): 350 return self.get_next_section() 351 return None 352 353 def find_file_sections(self): 354 file_line = self.file.readline() 355 while(len(file_line) > 0): 356 if(file_line.find('Sections:') == 0): 357 return True 358 file_line = self.file.readline() 359 return False 360 361 def read_section_line(self): 362 file_line = SectionLine(self.file.readline()) 363 while(file_line.length() > 0): 364 if(file_line.contains('SYMBOL TABLE:')): 365 return False 366 367 # Use split instead? # Place in a function 368 if(file_line.get_word(0).isdigit()): 369 file_line.append(self.file.readline()) 370 self.line = file_line 371 return True 372 else: 373 file_line.update(self.file.readline()) 374 return False 375 376 def parse_section_line(self): 377 # Use split instead? # Place in a function 378 if(self.line.get_word(0).isdigit()): 379 section = Section() 380 section.name = self.line.get_word(1) 381 section.size = int(self.line.get_word(2), 16) 382 section.vma = int(self.line.get_word(3), 16) 383 section.lma = int(self.line.get_word(4), 16) 384 section.flags = '' 385 for flag_name in flag_names: 386 if(self.line.contains(flag_name)): 387 section.flags = "%s_%s_" % (section.flags, flag_name) 388 return section 389 return None 390 391 def create_memory_map(self, lds_file): 392 memory = Memory() 393 with open(lds_file, 'r') as f: 394 text = f.read() 395 mem_map = re.findall(r"([A-Za-z0-9_]*?) *: *ORIGIN *= *([\(\)0-9A-Fa-f/\+\-x* ]*), *LENGTH *= *([\(\)0-9A-Fa-f/\+\-x* ]*)", text) 396 for item in mem_map: 397 memory.add_region(Region(item[0], int(eval(item[1])), int(eval(item[2])))) 398 399 memory.add_region(Region("Unexpected", 0x00000000, 0xFFFFFFFF)) 400 401 return memory 402 403 # Should this create and return a ProcessedLstFile, which contains the two 404 # function below? 405 def process(self): 406 section = self.get_first_section() 407 while(section is not None): 408 if(self.memory.add_section(section)): 409 if(section.is_loaded()): 410 load_section = section.get_load_section() 411 self.memory.add_section(load_section) 412 section = self.get_next_section() 413 414 def find_section(self, name): 415 return self.memory.find_section(name) 416 417 def display(self): 418 print("\n Memory Usage Summary...") 419 self.memory.filename = self.filename 420 self.memory.display() 421 print("\n ====================================================\n") 422 423 424 425display_depth = { 426 '.': 99, 427 'platform': 3, 428 'drivers': 6, 429 'hal': 6,} 430min_display_level = 1 431 432 433class SectionTree: 434 last_display_level = 0 435 last_section_size = 0 436 437 def __init__(self, name, level): 438 self.root = name 439 self.level = level 440 self.size = 0 441 self.children = [] 442 443 def get_name(self): 444 return self.root 445 446 def get_size(self): 447 return self.size 448 449 def get_children(self): 450 return self.children 451 452 def find(self, name): 453 if(self.root == name): 454 return self 455 for child in self.children: 456 found_tree = child.find(name) 457 if(found_tree is not None): 458 return found_tree 459 return None 460 461 def add_content(self, size, content): 462 assert len(content) > 0 463 464 if(size > 0): 465 self.size += size 466 467 if(len(content) > 1): 468 content_added = False 469 for child in self.children: 470 if(child.root == content[0]): 471 child.add_content(size, content[1:]) 472 content_added = True 473 if not content_added: 474 new_child = SectionTree(content[0], self.level + 1) 475 self.children.append(new_child) 476 new_child.add_content(size, content[1:]) 477 478 def display(self, depth): 479 if(self.root in display_depth): 480 depth = display_depth[self.root] 481 if(depth >= 0): 482 if(self.level < SectionTree.last_display_level): 483 print() 484 SectionTree.last_section_size = 0 485 SectionTree.last_display_level = self.level 486 487 if(self.level == min_display_level): 488 489 print("\n Section Total Size") 490 print("\n -------------------------------- -------- -----") 491 print("\n") 492 493 if(self.level >= min_display_level): 494 if(self.root.endswith(".c") or self.root.endswith(".s")): 495 name = self.root[:len(self.root) - 2] 496 else: 497 name = self.root 498 499 # A top level section name 500 if(self.level == min_display_level): 501 SectionTree.last_section_size = self.size 502 print(" %-32s %8d" % 503 (((self.level - min_display_level) * ' ') + name, 504 self.size)) 505 print() 506 # Last entry being dealt with so show its size 507 elif((depth == 0) or (len(self.children) == 0)): 508 SectionTree.last_section_size = 0 509 print(" %-42s %8d" % 510 (((self.level - min_display_level) * ' ') + name, 511 self.size)) 512 # Has children and size not the same as last size displayed 513 elif(len(self.children) > 0 and 514 (SectionTree.last_section_size != self.size)): 515 SectionTree.last_section_size = self.size 516 print(" %-32s %8d" % 517 (((self.level - min_display_level) * ' ') + name, 518 self.size)) 519 520 else: 521 print(" %-42s" % 522 (((self.level - min_display_level) * ' ') + name)) 523 524 if(depth > 0): 525 for child in self.children: 526 child.display(depth - 1) 527 528 def get_section_child( 529 self, 530 depth, 531 section_name, 532 tree_data_list, 533 start_flag, 534 end_flag): 535 536 if(self.root in display_depth): 537 depth = display_depth[self.root] 538 if(depth >= 0): 539 if(self.level < SectionTree.last_display_level): 540 SectionTree.last_section_size = 0 541 SectionTree.last_display_level = self.level 542 543 if(self.level == min_display_level): 544 if start_flag[-1] == 1: 545 end_flag[-1] = 1 546 547 if start_flag[-1] and end_flag[-1]: 548 return 1 549 550 if(self.level >= min_display_level): 551 if(self.root.endswith(".c") or self.root.endswith(".s")): 552 name = self.root[:len(self.root) - 2] 553 else: 554 name = self.root 555 556 if(self.level == min_display_level): 557 SectionTree.last_section_size = self.size 558 559 if name == section_name: 560 tree_data_list.append( 561 ["{0}:{1}".format(name, self.size)]) 562 start_flag[-1] = 1 563 564 # Last entry being dealt with so show its size 565 elif((depth == 0) or (len(self.children) == 0)): 566 SectionTree.last_section_size = 0 567 568 if start_flag[-1] == 1: 569 tree_data_list[-1].append( 570 "{0}:{1}".format(name, self.size)) 571 # Has children and size not the same as last size displayed 572 elif(len(self.children) > 0 and 573 (SectionTree.last_section_size != self.size)): 574 SectionTree.last_section_size = self.size 575 576 if start_flag[-1] == 1: 577 tree_data_list[-1].append( 578 "{0}:{1}".format(name, self.size)) 579 580 else: 581 if start_flag[-1] == 1: 582 tree_data_list[-1].append("{0}:{1}".format(name, "")) 583 584 if(depth > 0): 585 for child in self.children: 586 if(child.get_section_child(depth - 1, section_name, 587 tree_data_list, start_flag, end_flag)): 588 return 1 589 590 591class DuFile: 592 def __init__(self, fp_filename): 593 self.file = fp_filename 594 # Construct empty tree with '.' as root, since all pathnames in .du 595 # file 596 # begin in the same way 597 self.section_tree = SectionTree('.', 0) 598 599 def __del__(self): 600 self.file.close() 601 602 def get_next_section_content(self): 603 # e.g. "216 604 # ./.text/drivers/non-os/ipc/shared/ipc.c/ipc_send_message" 605 file_line = SectionLine(self.file.readline()) 606 if(file_line.length() > 0): 607 # Obtains size e.g. "216" 608 size = file_line.get_word(0) 609 # Last line of .du file begins with non-numeric characters 610 if(size.isdigit()): 611 size = int(size) 612 # Generates a list of words, separated by '/' e.g. 613 # ".",".text","drivers","non-os","ipc" etc 614 content = file_line.get_word(1).split(os.sep) 615 return (size, content) 616 return (0, None) 617 618 def get_first_section_content(self): 619 # Point to beginning of file 620 self.file.seek(0) 621 return self.get_next_section_content() 622 623 # Should this create and return a ProcessedDuFile, which will contain the 624 # two function below? 625 def process(self): 626 # Scan entire file contents 627 (size, content) = self.get_first_section_content() 628 while(content is not None): 629 self.section_tree.add_content(size, content[1:]) 630 (size, content) = self.get_next_section_content() 631 632 def find_section(self, name): 633 return self.section_tree.find(name) 634 635 def display(self): 636 print("\n Memory Usage Details...") 637 # Use a 0 depth here as it will be overridden by entries in 638 # display_depth 639 self.section_tree.display(0) 640 print("\n ====================================================\n") 641 642 def get_tree_size(self, section_name, path_name): 643 tree_data_list = [] 644 flag_list = [] 645 start_flag = [0] 646 end_flag = [0] 647 self.section_tree.get_section_child( 648 0, section_name, tree_data_list, start_flag, end_flag) 649 650 for i in range(len(tree_data_list[-1])): 651 if path_name[0] == tree_data_list[-1][i].split(":")[0]: 652 flag_list.append(1) 653 for path_index in range(len(path_name[1:])): 654 if path_name[1:][path_index] == tree_data_list[-1][ 655 i + 1 + path_index].split(":")[0]: 656 flag_list.append(1) 657 else: 658 flag_list.append(0) 659 if len(flag_list) == sum(flag_list): 660 size = tree_data_list[-1][i].split(":")[1] 661 if size == '': 662 size = 0 663 return int(size) 664 else: 665 flag_list = [] 666 667 668def display_mismatches(lst_file, du_file): 669 section_names = [ 670 "startup", 671 ".flash_version", 672 ".text", 673 ".ramtext", 674 ".data", 675 ".bss", 676 ".preserve", 677 ".sys_status", 678 ".ipc_mailbox", 679 "btc_ramtext", 680 "bth_ramtext", 681 "plt_ramtext", 682 "bth_share_ramtext", 683 "btc_data", 684 "bth_data", 685 "plt_data", 686 "btc_bss", 687 "bth_bss", 688 "plt_bss"] 689 690 print(" Mismatched section sizes...\n") 691 for section_name in section_names: 692 lst_section = lst_file.find_section(section_name) 693 du_section = du_file.find_section(section_name) 694 if lst_section is not None and du_section is not None: 695 lst_size = lst_section.get_size() 696 du_size = du_section.get_size() 697 if(lst_size != du_size): 698 print( 699 " %12s .lst file = %d (0x%X)" % 700 (section_name, lst_size, lst_size)) 701 print(" %12s .du file = %d (0x%X)" % 702 ("", du_size, du_size)) 703 print() 704 print(" ====================================================\n") 705 706 707def main(lst_file_name, du_file_name, lds_file, chip_name, chip_version=None): 708 with open(lst_file_name, "r") as fp_lst_file_name, open( 709 du_file_name, "r") as fp_du_file_name: 710 lst_file = LstFile( 711 fp_lst_file_name, 712 lst_file_name, 713 lds_file, 714 chip_name, 715 chip_version) 716 lst_file.process() 717 du_file = DuFile(fp_du_file_name) 718 du_file.process() 719 720 lst_file.display() 721 722 du_file.display() 723 display_mismatches(lst_file, du_file) 724 725 726if __name__ == "__main__": 727 if(len(sys.argv) == 5): 728 main(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]) 729 else: 730 print( 731 "Usage: %s <lstfile> <dufile> <chip>" % 732 os.path.basename( 733 sys.argv[0])) 734