1# Copyright (c) 2018 Foundries.io 2# 3# SPDX-License-Identifier: Apache-2.0 4 5import abc 6import argparse 7import os 8import pathlib 9import pickle 10import platform 11import shutil 12import subprocess 13import sys 14 15from elftools.elf.elffile import ELFFile 16 17from west import manifest 18from west.commands import Verbosity 19from west.util import quote_sh_list 20 21from build_helpers import find_build_dir, is_zephyr_build, \ 22 FIND_BUILD_DIR_DESCRIPTION 23from runners.core import BuildConfiguration 24from zcmake import CMakeCache 25from zephyr_ext_common import Forceable, ZEPHYR_SCRIPTS 26 27# This is needed to load edt.pickle files. 28sys.path.insert(0, str(ZEPHYR_SCRIPTS / 'dts' / 'python-devicetree' / 'src')) 29 30SIGN_DESCRIPTION = '''\ 31This command automates some of the drudgery of creating signed Zephyr 32binaries for chain-loading by a bootloader. 33 34In the simplest usage, run this from your build directory: 35 36 west sign -t your_tool -- ARGS_FOR_YOUR_TOOL 37 38The "ARGS_FOR_YOUR_TOOL" value can be any additional arguments you want to 39pass to the tool, such as the location of a signing key etc. Depending on 40which sort of ARGS_FOR_YOUR_TOOLS you use, the `--` separator/sentinel may 41not always be required. To avoid ambiguity and having to find and 42understand POSIX 12.2 Guideline 10, always use `--`. 43 44See tool-specific help below for details.''' 45 46SIGN_EPILOG = '''\ 47imgtool 48------- 49 50To build a signed binary you can load with MCUboot using imgtool, 51run this from your build directory: 52 53 west sign -t imgtool -- --key YOUR_SIGNING_KEY.pem 54 55For this to work, either imgtool must be installed (e.g. using pip3), 56or you must pass the path to imgtool.py using the -p option. 57 58Assuming your binary was properly built for processing and handling by 59imgtool, this creates zephyr.signed.bin and zephyr.signed.hex 60files which are ready for use by your bootloader. 61 62The version number, image header size, alignment, and slot sizes are 63determined from the build directory using .config and the device tree. 64As shown above, extra arguments after a '--' are passed to imgtool 65directly. 66 67rimage 68------ 69 70To create a signed binary with the rimage tool, run this from your build 71directory: 72 73 west sign -t rimage -- -k YOUR_SIGNING_KEY.pem 74 75For this to work, either rimage must be installed or you must pass 76the path to rimage using the -p option. 77 78You can also pass additional arguments to rimage thanks to [sign] and 79[rimage] sections in your west config file(s); this is especially useful 80when invoking west sign _indirectly_ through CMake/ninja. See how at 81https://docs.zephyrproject.org/latest/develop/west/sign.html 82''' 83 84class ToggleAction(argparse.Action): 85 86 def __call__(self, parser, args, ignored, option): 87 setattr(args, self.dest, not option.startswith('--no-')) 88 89 90class Sign(Forceable): 91 def __init__(self): 92 super(Sign, self).__init__( 93 'sign', 94 # Keep this in sync with the string in west-commands.yml. 95 'sign a Zephyr binary for bootloader chain-loading', 96 SIGN_DESCRIPTION, 97 accepts_unknown_args=False) 98 99 def do_add_parser(self, parser_adder): 100 parser = parser_adder.add_parser( 101 self.name, 102 epilog=SIGN_EPILOG, 103 help=self.help, 104 formatter_class=argparse.RawDescriptionHelpFormatter, 105 description=self.description) 106 107 parser.add_argument('-d', '--build-dir', 108 help=FIND_BUILD_DIR_DESCRIPTION) 109 parser.add_argument('-q', '--quiet', action='store_true', 110 help='suppress non-error output') 111 self.add_force_arg(parser) 112 113 # general options 114 group = parser.add_argument_group('tool control options') 115 group.add_argument('-t', '--tool', choices=['imgtool', 'rimage'], 116 help='''image signing tool name; imgtool and rimage 117 are currently supported (imgtool is deprecated)''') 118 group.add_argument('-p', '--tool-path', default=None, 119 help='''path to the tool itself, if needed''') 120 group.add_argument('-D', '--tool-data', default=None, 121 help='''path to a tool-specific data/configuration directory, if needed''') 122 group.add_argument('--if-tool-available', action='store_true', 123 help='''Do not fail if the rimage tool is not found or the rimage signing 124schema (rimage "target") is not defined in board.cmake.''') 125 group.add_argument('tool_args', nargs='*', metavar='tool_opt', 126 help='extra option(s) to pass to the signing tool') 127 128 # bin file options 129 group = parser.add_argument_group('binary (.bin) file options') 130 group.add_argument('--bin', '--no-bin', dest='gen_bin', nargs=0, 131 action=ToggleAction, 132 help='''produce a signed .bin file? 133 (default: yes, if supported and unsigned bin 134 exists)''') 135 group.add_argument('-B', '--sbin', metavar='BIN', 136 help='''signed .bin file name 137 (default: zephyr.signed.bin in the build 138 directory, next to zephyr.bin)''') 139 140 # hex file options 141 group = parser.add_argument_group('Intel HEX (.hex) file options') 142 group.add_argument('--hex', '--no-hex', dest='gen_hex', nargs=0, 143 action=ToggleAction, 144 help='''produce a signed .hex file? 145 (default: yes, if supported and unsigned hex 146 exists)''') 147 group.add_argument('-H', '--shex', metavar='HEX', 148 help='''signed .hex file name 149 (default: zephyr.signed.hex in the build 150 directory, next to zephyr.hex)''') 151 152 return parser 153 154 def do_run(self, args, ignored): 155 self.args = args # for check_force 156 157 # Find the build directory and parse .config and DT. 158 build_dir = find_build_dir(args.build_dir) 159 self.check_force(os.path.isdir(build_dir), 160 'no such build directory {}'.format(build_dir)) 161 self.check_force(is_zephyr_build(build_dir), 162 "build directory {} doesn't look like a Zephyr build " 163 'directory'.format(build_dir)) 164 build_conf = BuildConfiguration(build_dir) 165 166 if not args.tool: 167 args.tool = self.config_get('sign.tool') 168 169 # Decide on output formats. 170 formats = [] 171 bin_exists = build_conf.getboolean('CONFIG_BUILD_OUTPUT_BIN') 172 if args.gen_bin: 173 self.check_force(bin_exists, 174 '--bin given but CONFIG_BUILD_OUTPUT_BIN not set ' 175 "in build directory's ({}) .config". 176 format(build_dir)) 177 formats.append('bin') 178 elif args.gen_bin is None and bin_exists: 179 formats.append('bin') 180 181 hex_exists = build_conf.getboolean('CONFIG_BUILD_OUTPUT_HEX') 182 if args.gen_hex: 183 self.check_force(hex_exists, 184 '--hex given but CONFIG_BUILD_OUTPUT_HEX not set ' 185 "in build directory's ({}) .config". 186 format(build_dir)) 187 formats.append('hex') 188 elif args.gen_hex is None and hex_exists: 189 formats.append('hex') 190 191 # Delegate to the signer. 192 if args.tool == 'imgtool': 193 if args.if_tool_available: 194 self.die('imgtool does not support --if-tool-available') 195 signer = ImgtoolSigner() 196 elif args.tool == 'rimage': 197 signer = RimageSigner() 198 # (Add support for other signers here in elif blocks) 199 else: 200 if args.tool is None: 201 self.die('one --tool is required') 202 else: 203 self.die(f'invalid tool: {args.tool}') 204 205 signer.sign(self, build_dir, build_conf, formats) 206 207 208class Signer(abc.ABC): 209 '''Common abstract superclass for signers. 210 211 To add support for a new tool, subclass this and add support for 212 it in the Sign.do_run() method.''' 213 214 @abc.abstractmethod 215 def sign(self, command, build_dir, build_conf, formats): 216 '''Abstract method to perform a signature; subclasses must implement. 217 218 :param command: the Sign instance 219 :param build_dir: the build directory 220 :param build_conf: BuildConfiguration for build directory 221 :param formats: list of formats to generate ('bin', 'hex') 222 ''' 223 224 225class ImgtoolSigner(Signer): 226 227 def sign(self, command, build_dir, build_conf, formats): 228 if not formats: 229 return 230 231 args = command.args 232 b = pathlib.Path(build_dir) 233 234 command.wrn("west sign using imgtool is deprecated and will be removed in a future release") 235 236 imgtool = self.find_imgtool(command, args) 237 # The vector table offset and application version are set in Kconfig: 238 appver = self.get_cfg(command, build_conf, 'CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION') 239 vtoff = self.get_cfg(command, build_conf, 'CONFIG_ROM_START_OFFSET') 240 # Flash device write alignment and the partition's slot size 241 # come from devicetree: 242 flash = self.edt_flash_node(command, b, args.quiet) 243 align, addr, size = self.edt_flash_params(command, flash) 244 245 if not build_conf.getboolean('CONFIG_BOOTLOADER_MCUBOOT'): 246 command.wrn("CONFIG_BOOTLOADER_MCUBOOT is not set to y in " 247 f"{build_conf.path}; this probably won't work") 248 249 kernel = build_conf.get('CONFIG_KERNEL_BIN_NAME', 'zephyr') 250 251 if 'bin' in formats: 252 in_bin = b / 'zephyr' / f'{kernel}.bin' 253 if not in_bin.is_file(): 254 command.die(f"no unsigned .bin found at {in_bin}") 255 in_bin = os.fspath(in_bin) 256 else: 257 in_bin = None 258 if 'hex' in formats: 259 in_hex = b / 'zephyr' / f'{kernel}.hex' 260 if not in_hex.is_file(): 261 command.die(f"no unsigned .hex found at {in_hex}") 262 in_hex = os.fspath(in_hex) 263 else: 264 in_hex = None 265 266 if not args.quiet: 267 command.banner('image configuration:') 268 command.inf('partition offset: {0} (0x{0:x})'.format(addr)) 269 command.inf('partition size: {0} (0x{0:x})'.format(size)) 270 command.inf('rom start offset: {0} (0x{0:x})'.format(vtoff)) 271 272 # Base sign command. 273 sign_base = imgtool + ['sign', 274 '--version', str(appver), 275 '--align', str(align), 276 '--header-size', str(vtoff), 277 '--slot-size', str(size)] 278 sign_base.extend(args.tool_args) 279 280 if not args.quiet: 281 command.banner('signing binaries') 282 if in_bin: 283 out_bin = args.sbin or str(b / 'zephyr' / 'zephyr.signed.bin') 284 sign_bin = sign_base + [in_bin, out_bin] 285 if not args.quiet: 286 command.inf(f'unsigned bin: {in_bin}') 287 command.inf(f'signed bin: {out_bin}') 288 command.dbg(quote_sh_list(sign_bin)) 289 subprocess.check_call(sign_bin, stdout=subprocess.PIPE if args.quiet else None) 290 if in_hex: 291 out_hex = args.shex or str(b / 'zephyr' / 'zephyr.signed.hex') 292 sign_hex = sign_base + [in_hex, out_hex] 293 if not args.quiet: 294 command.inf(f'unsigned hex: {in_hex}') 295 command.inf(f'signed hex: {out_hex}') 296 command.dbg(quote_sh_list(sign_hex)) 297 subprocess.check_call(sign_hex, stdout=subprocess.PIPE if args.quiet else None) 298 299 @staticmethod 300 def find_imgtool(cmd, args): 301 if args.tool_path: 302 imgtool = args.tool_path 303 if not os.path.isfile(imgtool): 304 cmd.die(f'--tool-path {imgtool}: no such file') 305 else: 306 imgtool = shutil.which('imgtool') or shutil.which('imgtool.py') 307 if not imgtool: 308 cmd.die('imgtool not found; either install it', 309 '(e.g. "pip3 install imgtool") or provide --tool-path') 310 311 if platform.system() == 'Windows' and imgtool.endswith('.py'): 312 # Windows users may not be able to run .py files 313 # as executables in subprocesses, regardless of 314 # what the mode says. Always run imgtool as 315 # 'python path/to/imgtool.py' instead of 316 # 'path/to/imgtool.py' in these cases. 317 # https://github.com/zephyrproject-rtos/zephyr/issues/31876 318 return [sys.executable, imgtool] 319 320 return [imgtool] 321 322 @staticmethod 323 def get_cfg(command, build_conf, item): 324 try: 325 return build_conf[item] 326 except KeyError: 327 command.check_force( 328 False, "build .config is missing a {} value".format(item)) 329 return None 330 331 @staticmethod 332 def edt_flash_node(cmd, b, quiet=False): 333 # Get the EDT Node corresponding to the zephyr,flash chosen DT 334 # node; 'b' is the build directory as a pathlib object. 335 336 # Ensure the build directory has a compiled DTS file 337 # where we expect it to be. 338 dts = b / 'zephyr' / 'zephyr.dts' 339 if not quiet: 340 cmd.dbg('DTS file:', dts, level=Verbosity.DBG_MORE) 341 edt_pickle = b / 'zephyr' / 'edt.pickle' 342 if not edt_pickle.is_file(): 343 cmd.die("can't load devicetree; expected to find:", edt_pickle) 344 345 # Load the devicetree. 346 with open(edt_pickle, 'rb') as f: 347 edt = pickle.load(f) 348 349 # By convention, the zephyr,flash chosen node contains the 350 # partition information about the zephyr image to sign. 351 flash = edt.chosen_node('zephyr,flash') 352 if not flash: 353 cmd.die('devicetree has no chosen zephyr,flash node;', 354 "can't infer flash write block or slot0_partition slot sizes") 355 356 return flash 357 358 @staticmethod 359 def edt_flash_params(cmd, flash): 360 # Get the flash device's write alignment and offset from the 361 # slot0_partition and the size from slot1_partition , out of the 362 # build directory's devicetree. slot1_partition size is used, 363 # when available, because in swap-move mode it can be one sector 364 # smaller. When not available, fallback to slot0_partition (single slot dfu). 365 366 # The node must have a "partitions" child node, which in turn 367 # must have child nodes with label slot0_partition and may have a child node 368 # with label slot1_partition. By convention, the slots for consumption by 369 # imgtool are linked into these partitions. 370 if 'partitions' not in flash.children: 371 cmd.die("DT zephyr,flash chosen node has no partitions,", 372 "can't find partitions for MCUboot slots") 373 374 partitions = flash.children['partitions'] 375 slots = { 376 label: node for node in partitions.children.values() 377 for label in node.labels 378 if label in set(['slot0_partition', 'slot1_partition']) 379 } 380 381 if 'slot0_partition' not in slots: 382 cmd.die("DT zephyr,flash chosen node has no slot0_partition partition,", 383 "can't determine its address") 384 385 # Die on missing or zero alignment or slot_size. 386 if "write-block-size" not in flash.props: 387 cmd.die('DT zephyr,flash node has no write-block-size;', 388 "can't determine imgtool write alignment") 389 align = flash.props['write-block-size'].val 390 if align == 0: 391 cmd.die('expected nonzero flash alignment, but got ' 392 'DT flash device write-block-size {}'.format(align)) 393 394 # The partitions node, and its subnode, must provide 395 # the size of slot1_partition or slot0_partition partition via the regs property. 396 slot_key = 'slot1_partition' if 'slot1_partition' in slots else 'slot0_partition' 397 if not slots[slot_key].regs: 398 cmd.die(f'{slot_key} flash partition has no regs property;', 399 "can't determine size of slot") 400 401 # always use addr of slot0_partition, which is where slots are run 402 addr = slots['slot0_partition'].regs[0].addr 403 404 size = slots[slot_key].regs[0].size 405 if size == 0: 406 cmd.die('expected nonzero slot size for {}'.format(slot_key)) 407 408 return (align, addr, size) 409 410class RimageSigner(Signer): 411 412 def rimage_config_dir(self): 413 'Returns the rimage/config/ directory with the highest precedence' 414 args = self.command.args 415 if args.tool_data: 416 conf_dir = pathlib.Path(args.tool_data) 417 elif self.cmake_cache.get('RIMAGE_CONFIG_PATH'): 418 conf_dir = pathlib.Path(self.cmake_cache['RIMAGE_CONFIG_PATH']) 419 else: 420 conf_dir = self.sof_src_dir / 'tools' / 'rimage' / 'config' 421 self.command.dbg(f'rimage config directory={conf_dir}') 422 return conf_dir 423 424 def preprocess_toml(self, config_dir, toml_basename, subdir): 425 'Runs the C pre-processor on config_dir/toml_basename.h' 426 427 compiler_path = self.cmake_cache.get("CMAKE_C_COMPILER") 428 preproc_cmd = [compiler_path, '-E', str(config_dir / (toml_basename + '.h'))] 429 # -P removes line markers to keep the .toml output reproducible. To 430 # trace #includes, temporarily comment out '-P' (-f*-prefix-map 431 # unfortunately don't seem to make any difference here and they're 432 # gcc-specific) 433 preproc_cmd += ['-P'] 434 435 # "REM" escapes _leading_ '#' characters from cpp and allows 436 # such comments to be preserved in generated/*.toml files: 437 # 438 # REM # my comment... 439 # 440 # Note _trailing_ '#' characters and comments are ignored by cpp 441 # and don't need any REM trick. 442 preproc_cmd += ['-DREM='] 443 444 preproc_cmd += ['-I', str(self.sof_src_dir / 'src')] 445 preproc_cmd += ['-imacros', 446 str(pathlib.Path('zephyr') / 'include' / 'generated' / 'zephyr' / 'autoconf.h')] 447 # Need to preprocess the TOML file twice: once with 448 # LLEXT_FORCE_ALL_MODULAR defined and once without it 449 full_preproc_cmd = preproc_cmd + ['-o', str(subdir / 'rimage_config_full.toml'), '-DLLEXT_FORCE_ALL_MODULAR'] 450 preproc_cmd += ['-o', str(subdir / 'rimage_config.toml')] 451 self.command.inf(quote_sh_list(preproc_cmd)) 452 subprocess.run(preproc_cmd, check=True, cwd=self.build_dir) 453 subprocess.run(full_preproc_cmd, check=True, cwd=self.build_dir) 454 455 def sign(self, command, build_dir, build_conf, formats): 456 self.command = command 457 args = command.args 458 459 b = pathlib.Path(build_dir) 460 self.build_dir = b 461 cache = CMakeCache.from_build_dir(build_dir) 462 self.cmake_cache = cache 463 464 # Warning: RIMAGE_TARGET in Zephyr is a duplicate of 465 # CONFIG_RIMAGE_SIGNING_SCHEMA in SOF. 466 target = cache.get('RIMAGE_TARGET') 467 468 if not target: 469 msg = 'rimage target not defined in board.cmake' 470 if args.if_tool_available: 471 command.inf(msg) 472 sys.exit(0) 473 else: 474 command.die(msg) 475 476 kernel_name = build_conf.get('CONFIG_KERNEL_BIN_NAME', 'zephyr') 477 478 bootloader = None 479 cold = None 480 kernel = str(b / 'zephyr' / f'{kernel_name}.elf') 481 out_bin = str(b / 'zephyr' / f'{kernel_name}.ri') 482 out_xman = str(b / 'zephyr' / f'{kernel_name}.ri.xman') 483 out_tmp = str(b / 'zephyr' / f'{kernel_name}.rix') 484 485 # Intel platforms generate a "boot.mod" and "main.mod" as 486 # separate intermediates to use. Other platforms just use 487 # zephyr.elf directly. 488 if os.path.exists(str(b / 'zephyr' / 'boot.mod')): 489 bootloader = str(b / 'zephyr' / 'boot.mod') 490 if os.path.exists(str(b / 'zephyr' / 'cold.mod')): 491 cold = str(b / 'zephyr' / 'cold.mod') 492 with open(cold, 'rb') as f_cold: 493 elf = ELFFile(f_cold) 494 if elf.get_section_by_name('.cold') is None: 495 cold = None 496 if os.path.exists(str(b / 'zephyr' / 'main.mod')): 497 kernel = str(b / 'zephyr' / 'main.mod') 498 499 # Clean any stale output. This is especially important when using --if-tool-available 500 # (but not just) 501 for o in [ out_bin, out_xman, out_tmp ]: 502 pathlib.Path(o).unlink(missing_ok=True) 503 504 tool_path = ( 505 args.tool_path if args.tool_path else 506 command.config_get('rimage.path', None) 507 ) 508 err_prefix = '--tool-path' if args.tool_path else 'west config' 509 510 if tool_path: 511 command.check_force(shutil.which(tool_path), 512 f'{err_prefix} {tool_path}: not an executable') 513 else: 514 tool_path = shutil.which('rimage') 515 if not tool_path: 516 err_msg = 'rimage not found; either install it or provide --tool-path' 517 if args.if_tool_available: 518 command.wrn(err_msg) 519 command.wrn('zephyr binary _not_ signed!') 520 return 521 else: 522 command.die(err_msg) 523 524 #### -c sof/rimage/config/signing_schema.toml #### 525 526 if not args.quiet: 527 command.inf('Signing with tool {}'.format(tool_path)) 528 529 try: 530 sof_proj = command.manifest.get_projects(['sof'], allow_paths=False) 531 sof_src_dir = pathlib.Path(sof_proj[0].abspath) 532 except ValueError: # sof is the manifest 533 sof_src_dir = pathlib.Path(manifest.manifest_path()).parent 534 535 self.sof_src_dir = sof_src_dir 536 537 538 command.inf('Signing for SOC target ' + target) 539 540 # FIXME: deprecate --no-manifest and replace it with a much 541 # simpler and more direct `-- -e` which the user can _already_ 542 # pass today! With unclear consequences right now... 543 if '--no-manifest' in args.tool_args: 544 no_manifest = True 545 args.tool_args.remove('--no-manifest') 546 else: 547 no_manifest = False 548 549 # Non-SOF build does not have extended manifest data for 550 # rimage to process, which might result in rimage error. 551 # So skip it when not doing SOF builds. 552 is_sof_build = build_conf.getboolean('CONFIG_SOF') 553 if not is_sof_build: 554 no_manifest = True 555 556 if no_manifest: 557 extra_ri_args = [ ] 558 else: 559 extra_ri_args = ['-e'] 560 561 sign_base = [tool_path] 562 563 # Align rimage verbosity. 564 # Sub-command arg 'west sign -q' takes precedence over west '-v' 565 if not args.quiet and args.verbose: 566 sign_base += ['-v'] * args.verbose 567 568 # Order is important 569 components = [ ] if bootloader is None else [ bootloader ] 570 if cold is not None: 571 components += [ cold ] 572 components += [ kernel ] 573 574 sign_config_extra_args = command.config_get_words('rimage.extra-args', []) 575 576 if '-k' not in sign_config_extra_args + args.tool_args: 577 # rimage requires a key argument even when it does not sign 578 cmake_default_key = cache.get('RIMAGE_SIGN_KEY', 'key placeholder from sign.py') 579 extra_ri_args += [ '-k', str(sof_src_dir / 'keys' / cmake_default_key) ] 580 581 if args.tool_data and '-c' in args.tool_args: 582 command.wrn('--tool-data ' + args.tool_data + ' ignored! Overridden by: -- -c ... ') 583 584 if '-c' not in sign_config_extra_args + args.tool_args: 585 conf_dir = self.rimage_config_dir() 586 toml_basename = target + '.toml' 587 if ((conf_dir / toml_basename).exists() and 588 (conf_dir / (toml_basename + '.h')).exists()): 589 command.die(f"Cannot have both {toml_basename + '.h'} and {toml_basename} in {conf_dir}") 590 591 if (conf_dir / (toml_basename + '.h')).exists(): 592 generated_subdir = pathlib.Path('zephyr') / 'misc' / 'generated' 593 self.preprocess_toml(conf_dir, toml_basename, generated_subdir) 594 extra_ri_args += ['-c', str(b / generated_subdir / 'rimage_config.toml')] 595 else: 596 toml_dir = conf_dir 597 extra_ri_args += ['-c', str(toml_dir / toml_basename)] 598 599 # Warning: while not officially supported (yet?), the rimage --option that is last 600 # on the command line currently wins in case of duplicate options. So pay 601 # attention to the _args order below. 602 sign_base += (['-o', out_bin] + sign_config_extra_args + 603 extra_ri_args + args.tool_args + components) 604 605 command.inf(quote_sh_list(sign_base)) 606 subprocess.check_call(sign_base) 607 608 if no_manifest: 609 filenames = [out_bin] 610 else: 611 filenames = [out_xman, out_bin] 612 if not args.quiet: 613 command.inf('Prefixing ' + out_bin + ' with manifest ' + out_xman) 614 with open(out_tmp, 'wb') as outfile: 615 for fname in filenames: 616 with open(fname, 'rb') as infile: 617 outfile.write(infile.read()) 618 619 os.remove(out_bin) 620 os.rename(out_tmp, out_bin) 621