aboutsummaryrefslogtreecommitdiff
path: root/chromiumos_image_diff.py
diff options
context:
space:
mode:
Diffstat (limited to 'chromiumos_image_diff.py')
-rwxr-xr-xchromiumos_image_diff.py695
1 files changed, 373 insertions, 322 deletions
diff --git a/chromiumos_image_diff.py b/chromiumos_image_diff.py
index 3d54100d..ed840cb0 100755
--- a/chromiumos_image_diff.py
+++ b/chromiumos_image_diff.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
-# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# Copyright 2019 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -20,9 +20,8 @@
And this script should be executed outside chroot.
"""
-from __future__ import print_function
-__author__ = 'shenhan@google.com (Han Shen)'
+__author__ = "shenhan@google.com (Han Shen)"
import argparse
import os
@@ -30,338 +29,390 @@ import re
import sys
import tempfile
-import image_chromeos
from cros_utils import command_executer
from cros_utils import logger
from cros_utils import misc
+import image_chromeos
class CrosImage(object):
- """A cros image object."""
-
- def __init__(self, image, chromeos_root, no_unmount):
- self.image = image
- self.chromeos_root = chromeos_root
- self.mounted = False
- self._ce = command_executer.GetCommandExecuter()
- self.logger = logger.GetLogger()
- self.elf_files = []
- self.no_unmount = no_unmount
- self.unmount_script = ''
- self.stateful = ''
- self.rootfs = ''
-
- def MountImage(self, mount_basename):
- """Mount/unpack the image."""
-
- if mount_basename:
- self.rootfs = '/tmp/{0}.rootfs'.format(mount_basename)
- self.stateful = '/tmp/{0}.stateful'.format(mount_basename)
- self.unmount_script = '/tmp/{0}.unmount.sh'.format(mount_basename)
- else:
- self.rootfs = tempfile.mkdtemp(
- suffix='.rootfs', prefix='chromiumos_image_diff')
- ## rootfs is like /tmp/tmpxyz012.rootfs.
- match = re.match(r'^(.*)\.rootfs$', self.rootfs)
- basename = match.group(1)
- self.stateful = basename + '.stateful'
- os.mkdir(self.stateful)
- self.unmount_script = '{0}.unmount.sh'.format(basename)
-
- self.logger.LogOutput('Mounting "{0}" onto "{1}" and "{2}"'.format(
- self.image, self.rootfs, self.stateful))
- ## First of all creating an unmount image
- self.CreateUnmountScript()
- command = image_chromeos.GetImageMountCommand(self.image, self.rootfs,
- self.stateful)
- rv = self._ce.RunCommand(command, print_to_console=True)
- self.mounted = (rv == 0)
- if not self.mounted:
- self.logger.LogError('Failed to mount "{0}" onto "{1}" and "{2}".'.format(
- self.image, self.rootfs, self.stateful))
- return self.mounted
-
- def CreateUnmountScript(self):
- command = ('sudo umount {r}/usr/local {r}/usr/share/oem '
- '{r}/var {r}/mnt/stateful_partition {r}; sudo umount {s} ; '
- 'rmdir {r} ; rmdir {s}\n').format(
- r=self.rootfs, s=self.stateful)
- f = open(self.unmount_script, 'w', encoding='utf-8')
- f.write(command)
- f.close()
- self._ce.RunCommand(
- 'chmod +x {}'.format(self.unmount_script), print_to_console=False)
- self.logger.LogOutput('Created an unmount script - "{0}"'.format(
- self.unmount_script))
-
- def UnmountImage(self):
- """Unmount the image and delete mount point."""
-
- self.logger.LogOutput('Unmounting image "{0}" from "{1}" and "{2}"'.format(
- self.image, self.rootfs, self.stateful))
- if self.mounted:
- command = 'bash "{0}"'.format(self.unmount_script)
- if self.no_unmount:
- self.logger.LogOutput(('Please unmount manually - \n'
- '\t bash "{0}"'.format(self.unmount_script)))
- else:
- if self._ce.RunCommand(command, print_to_console=True) == 0:
- self._ce.RunCommand('rm {0}'.format(self.unmount_script))
- self.mounted = False
- self.rootfs = None
- self.stateful = None
- self.unmount_script = None
-
- return not self.mounted
-
- def FindElfFiles(self):
- """Find all elf files for the image.
-
- Returns:
- Always true
- """
-
- self.logger.LogOutput('Finding all elf files in "{0}" ...'.format(
- self.rootfs))
- # Note '\;' must be prefixed by 'r'.
- command = ('find "{0}" -type f -exec '
- 'bash -c \'file -b "{{}}" | grep -q "ELF"\''
- r' \; '
- r'-exec echo "{{}}" \;').format(self.rootfs)
- self.logger.LogCmd(command)
- _, out, _ = self._ce.RunCommandWOutput(command, print_to_console=False)
- self.elf_files = out.splitlines()
- self.logger.LogOutput('Total {0} elf files found.'.format(
- len(self.elf_files)))
- return True
+ """A cros image object."""
+
+ def __init__(self, image, chromeos_root, no_unmount):
+ self.image = image
+ self.chromeos_root = chromeos_root
+ self.mounted = False
+ self._ce = command_executer.GetCommandExecuter()
+ self.logger = logger.GetLogger()
+ self.elf_files = []
+ self.no_unmount = no_unmount
+ self.unmount_script = ""
+ self.stateful = ""
+ self.rootfs = ""
+
+ def MountImage(self, mount_basename):
+ """Mount/unpack the image."""
+
+ if mount_basename:
+ self.rootfs = "/tmp/{0}.rootfs".format(mount_basename)
+ self.stateful = "/tmp/{0}.stateful".format(mount_basename)
+ self.unmount_script = "/tmp/{0}.unmount.sh".format(mount_basename)
+ else:
+ self.rootfs = tempfile.mkdtemp(
+ suffix=".rootfs", prefix="chromiumos_image_diff"
+ )
+ ## rootfs is like /tmp/tmpxyz012.rootfs.
+ match = re.match(r"^(.*)\.rootfs$", self.rootfs)
+ basename = match.group(1)
+ self.stateful = basename + ".stateful"
+ os.mkdir(self.stateful)
+ self.unmount_script = "{0}.unmount.sh".format(basename)
+
+ self.logger.LogOutput(
+ 'Mounting "{0}" onto "{1}" and "{2}"'.format(
+ self.image, self.rootfs, self.stateful
+ )
+ )
+ ## First of all creating an unmount image
+ self.CreateUnmountScript()
+ command = image_chromeos.GetImageMountCommand(
+ self.image, self.rootfs, self.stateful
+ )
+ rv = self._ce.RunCommand(command, print_to_console=True)
+ self.mounted = rv == 0
+ if not self.mounted:
+ self.logger.LogError(
+ 'Failed to mount "{0}" onto "{1}" and "{2}".'.format(
+ self.image, self.rootfs, self.stateful
+ )
+ )
+ return self.mounted
+
+ def CreateUnmountScript(self):
+ command = (
+ "sudo umount {r}/usr/local {r}/usr/share/oem "
+ "{r}/var {r}/mnt/stateful_partition {r}; sudo umount {s} ; "
+ "rmdir {r} ; rmdir {s}\n"
+ ).format(r=self.rootfs, s=self.stateful)
+ f = open(self.unmount_script, "w", encoding="utf-8")
+ f.write(command)
+ f.close()
+ self._ce.RunCommand(
+ "chmod +x {}".format(self.unmount_script), print_to_console=False
+ )
+ self.logger.LogOutput(
+ 'Created an unmount script - "{0}"'.format(self.unmount_script)
+ )
+
+ def UnmountImage(self):
+ """Unmount the image and delete mount point."""
+
+ self.logger.LogOutput(
+ 'Unmounting image "{0}" from "{1}" and "{2}"'.format(
+ self.image, self.rootfs, self.stateful
+ )
+ )
+ if self.mounted:
+ command = 'bash "{0}"'.format(self.unmount_script)
+ if self.no_unmount:
+ self.logger.LogOutput(
+ (
+ "Please unmount manually - \n"
+ '\t bash "{0}"'.format(self.unmount_script)
+ )
+ )
+ else:
+ if self._ce.RunCommand(command, print_to_console=True) == 0:
+ self._ce.RunCommand("rm {0}".format(self.unmount_script))
+ self.mounted = False
+ self.rootfs = None
+ self.stateful = None
+ self.unmount_script = None
+
+ return not self.mounted
+
+ def FindElfFiles(self):
+ """Find all elf files for the image.
+
+ Returns:
+ Always true
+ """
+
+ self.logger.LogOutput(
+ 'Finding all elf files in "{0}" ...'.format(self.rootfs)
+ )
+ # Note '\;' must be prefixed by 'r'.
+ command = (
+ 'find "{0}" -type f -exec '
+ 'bash -c \'file -b "{{}}" | grep -q "ELF"\''
+ r" \; "
+ r'-exec echo "{{}}" \;'
+ ).format(self.rootfs)
+ self.logger.LogCmd(command)
+ _, out, _ = self._ce.RunCommandWOutput(command, print_to_console=False)
+ self.elf_files = out.splitlines()
+ self.logger.LogOutput(
+ "Total {0} elf files found.".format(len(self.elf_files))
+ )
+ return True
class ImageComparator(object):
- """A class that wraps comparsion actions."""
-
- def __init__(self, images, diff_file):
- self.images = images
- self.logger = logger.GetLogger()
- self.diff_file = diff_file
- self.tempf1 = None
- self.tempf2 = None
-
- def Cleanup(self):
- if self.tempf1 and self.tempf2:
- command_executer.GetCommandExecuter().RunCommand('rm {0} {1}'.format(
- self.tempf1, self.tempf2))
- logger.GetLogger('Removed "{0}" and "{1}".'.format(
- self.tempf1, self.tempf2))
-
- def CheckElfFileSetEquality(self):
- """Checking whether images have exactly number of elf files."""
-
- self.logger.LogOutput('Checking elf file equality ...')
- i1 = self.images[0]
- i2 = self.images[1]
- t1 = i1.rootfs + '/'
- elfset1 = {e.replace(t1, '') for e in i1.elf_files}
- t2 = i2.rootfs + '/'
- elfset2 = {e.replace(t2, '') for e in i2.elf_files}
- dif1 = elfset1.difference(elfset2)
- msg = None
- if dif1:
- msg = 'The following files are not in "{image}" - "{rootfs}":\n'.format(
- image=i2.image, rootfs=i2.rootfs)
- for d in dif1:
- msg += '\t' + d + '\n'
- dif2 = elfset2.difference(elfset1)
- if dif2:
- msg = 'The following files are not in "{image}" - "{rootfs}":\n'.format(
- image=i1.image, rootfs=i1.rootfs)
- for d in dif2:
- msg += '\t' + d + '\n'
- if msg:
- self.logger.LogError(msg)
- return False
- return True
-
- def CompareImages(self):
- """Do the comparsion work."""
-
- if not self.CheckElfFileSetEquality():
- return False
-
- mismatch_list = []
- match_count = 0
- i1 = self.images[0]
- i2 = self.images[1]
- self.logger.LogOutput('Start comparing {0} elf file by file ...'.format(
- len(i1.elf_files)))
- ## Note - i1.elf_files and i2.elf_files have exactly the same entries here.
-
- ## Create 2 temp files to be used for all disassembed files.
- handle, self.tempf1 = tempfile.mkstemp()
- os.close(handle) # We do not need the handle
- handle, self.tempf2 = tempfile.mkstemp()
- os.close(handle)
-
- cmde = command_executer.GetCommandExecuter()
- for elf1 in i1.elf_files:
- tmp_rootfs = i1.rootfs + '/'
- f1 = elf1.replace(tmp_rootfs, '')
- full_path1 = elf1
- full_path2 = elf1.replace(i1.rootfs, i2.rootfs)
-
- if full_path1 == full_path2:
- self.logger.LogError(
- "Error: We're comparing the SAME file - {0}".format(f1))
- continue
-
- command = (
- 'objdump -d "{f1}" > {tempf1} ; '
- 'objdump -d "{f2}" > {tempf2} ; '
- # Remove path string inside the dissemble
- "sed -i 's!{rootfs1}!!g' {tempf1} ; "
- "sed -i 's!{rootfs2}!!g' {tempf2} ; "
- 'diff {tempf1} {tempf2} 1>/dev/null 2>&1').format(
- f1=full_path1,
- f2=full_path2,
- rootfs1=i1.rootfs,
- rootfs2=i2.rootfs,
- tempf1=self.tempf1,
- tempf2=self.tempf2)
- ret = cmde.RunCommand(command, print_to_console=False)
- if ret != 0:
- self.logger.LogOutput('*** Not match - "{0}" "{1}"'.format(
- full_path1, full_path2))
- mismatch_list.append(f1)
- if self.diff_file:
- command = ('echo "Diffs of disassemble of \"{f1}\" and \"{f2}\"" '
- '>> {diff_file} ; diff {tempf1} {tempf2} '
- '>> {diff_file}').format(
- f1=full_path1,
- f2=full_path2,
- diff_file=self.diff_file,
- tempf1=self.tempf1,
- tempf2=self.tempf2)
- cmde.RunCommand(command, print_to_console=False)
- else:
- match_count += 1
- ## End of comparing every elf files.
-
- if not mismatch_list:
- self.logger.LogOutput(
- '** COOL, ALL {0} BINARIES MATCHED!! **'.format(match_count))
- return True
-
- mismatch_str = 'Found {0} mismatch:\n'.format(len(mismatch_list))
- for b in mismatch_list:
- mismatch_str += '\t' + b + '\n'
-
- self.logger.LogOutput(mismatch_str)
- return False
+ """A class that wraps comparsion actions."""
+
+ def __init__(self, images, diff_file):
+ self.images = images
+ self.logger = logger.GetLogger()
+ self.diff_file = diff_file
+ self.tempf1 = None
+ self.tempf2 = None
+
+ def Cleanup(self):
+ if self.tempf1 and self.tempf2:
+ command_executer.GetCommandExecuter().RunCommand(
+ "rm {0} {1}".format(self.tempf1, self.tempf2)
+ )
+ logger.GetLogger(
+ 'Removed "{0}" and "{1}".'.format(self.tempf1, self.tempf2)
+ )
+
+ def CheckElfFileSetEquality(self):
+ """Checking whether images have exactly number of elf files."""
+
+ self.logger.LogOutput("Checking elf file equality ...")
+ i1 = self.images[0]
+ i2 = self.images[1]
+ t1 = i1.rootfs + "/"
+ elfset1 = {e.replace(t1, "") for e in i1.elf_files}
+ t2 = i2.rootfs + "/"
+ elfset2 = {e.replace(t2, "") for e in i2.elf_files}
+ dif1 = elfset1.difference(elfset2)
+ msg = None
+ if dif1:
+ msg = 'The following files are not in "{image}" - "{rootfs}":\n'.format(
+ image=i2.image, rootfs=i2.rootfs
+ )
+ for d in dif1:
+ msg += "\t" + d + "\n"
+ dif2 = elfset2.difference(elfset1)
+ if dif2:
+ msg = 'The following files are not in "{image}" - "{rootfs}":\n'.format(
+ image=i1.image, rootfs=i1.rootfs
+ )
+ for d in dif2:
+ msg += "\t" + d + "\n"
+ if msg:
+ self.logger.LogError(msg)
+ return False
+ return True
+
+ def CompareImages(self):
+ """Do the comparsion work."""
+
+ if not self.CheckElfFileSetEquality():
+ return False
+
+ mismatch_list = []
+ match_count = 0
+ i1 = self.images[0]
+ i2 = self.images[1]
+ self.logger.LogOutput(
+ "Start comparing {0} elf file by file ...".format(len(i1.elf_files))
+ )
+ ## Note - i1.elf_files and i2.elf_files have exactly the same entries here.
+
+ ## Create 2 temp files to be used for all disassembed files.
+ handle, self.tempf1 = tempfile.mkstemp()
+ os.close(handle) # We do not need the handle
+ handle, self.tempf2 = tempfile.mkstemp()
+ os.close(handle)
+
+ cmde = command_executer.GetCommandExecuter()
+ for elf1 in i1.elf_files:
+ tmp_rootfs = i1.rootfs + "/"
+ f1 = elf1.replace(tmp_rootfs, "")
+ full_path1 = elf1
+ full_path2 = elf1.replace(i1.rootfs, i2.rootfs)
+
+ if full_path1 == full_path2:
+ self.logger.LogError(
+ "Error: We're comparing the SAME file - {0}".format(f1)
+ )
+ continue
+
+ command = (
+ 'objdump -d "{f1}" > {tempf1} ; '
+ 'objdump -d "{f2}" > {tempf2} ; '
+ # Remove path string inside the dissemble
+ "sed -i 's!{rootfs1}!!g' {tempf1} ; "
+ "sed -i 's!{rootfs2}!!g' {tempf2} ; "
+ "diff {tempf1} {tempf2} 1>/dev/null 2>&1"
+ ).format(
+ f1=full_path1,
+ f2=full_path2,
+ rootfs1=i1.rootfs,
+ rootfs2=i2.rootfs,
+ tempf1=self.tempf1,
+ tempf2=self.tempf2,
+ )
+ ret = cmde.RunCommand(command, print_to_console=False)
+ if ret != 0:
+ self.logger.LogOutput(
+ '*** Not match - "{0}" "{1}"'.format(full_path1, full_path2)
+ )
+ mismatch_list.append(f1)
+ if self.diff_file:
+ command = (
+ 'echo "Diffs of disassemble of "{f1}" and "{f2}"" '
+ ">> {diff_file} ; diff {tempf1} {tempf2} "
+ ">> {diff_file}"
+ ).format(
+ f1=full_path1,
+ f2=full_path2,
+ diff_file=self.diff_file,
+ tempf1=self.tempf1,
+ tempf2=self.tempf2,
+ )
+ cmde.RunCommand(command, print_to_console=False)
+ else:
+ match_count += 1
+ ## End of comparing every elf files.
+
+ if not mismatch_list:
+ self.logger.LogOutput(
+ "** COOL, ALL {0} BINARIES MATCHED!! **".format(match_count)
+ )
+ return True
+
+ mismatch_str = "Found {0} mismatch:\n".format(len(mismatch_list))
+ for b in mismatch_list:
+ mismatch_str += "\t" + b + "\n"
+
+ self.logger.LogOutput(mismatch_str)
+ return False
def Main(argv):
- """The main function."""
-
- command_executer.InitCommandExecuter()
- images = []
-
- parser = argparse.ArgumentParser()
- parser.add_argument(
- '--no_unmount',
- action='store_true',
- dest='no_unmount',
- default=False,
- help='Do not unmount after finish, this is useful for debugging.')
- parser.add_argument(
- '--chromeos_root',
- dest='chromeos_root',
- default=None,
- action='store',
- help=('[Optional] Specify a chromeos tree instead of '
- 'deducing it from image path so that we can compare '
- '2 images that are downloaded.'))
- parser.add_argument(
- '--mount_basename',
- dest='mount_basename',
- default=None,
- action='store',
- help=('Specify a meaningful name for the mount point. With this being '
+ """The main function."""
+
+ command_executer.InitCommandExecuter()
+ images = []
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "--no_unmount",
+ action="store_true",
+ dest="no_unmount",
+ default=False,
+ help="Do not unmount after finish, this is useful for debugging.",
+ )
+ parser.add_argument(
+ "--chromeos_root",
+ dest="chromeos_root",
+ default=None,
+ action="store",
+ help=(
+ "[Optional] Specify a chromeos tree instead of "
+ "deducing it from image path so that we can compare "
+ "2 images that are downloaded."
+ ),
+ )
+ parser.add_argument(
+ "--mount_basename",
+ dest="mount_basename",
+ default=None,
+ action="store",
+ help=(
+ "Specify a meaningful name for the mount point. With this being "
'set, the mount points would be "/tmp/mount_basename.x.rootfs" '
- ' and "/tmp/mount_basename.x.stateful". (x is 1 or 2).'))
- parser.add_argument(
- '--diff_file',
- dest='diff_file',
- default=None,
- help='Dumping all the diffs (if any) to the diff file')
- parser.add_argument(
- '--image1',
- dest='image1',
- default=None,
- required=True,
- help=('Image 1 file name.'))
- parser.add_argument(
- '--image2',
- dest='image2',
- default=None,
- required=True,
- help=('Image 2 file name.'))
- options = parser.parse_args(argv[1:])
-
- if options.mount_basename and options.mount_basename.find('/') >= 0:
- logger.GetLogger().LogError(
- '"--mount_basename" must be a name, not a path.')
- parser.print_help()
- return 1
-
- result = False
- image_comparator = None
- try:
- for i, image_path in enumerate([options.image1, options.image2], start=1):
- image_path = os.path.realpath(image_path)
- if not os.path.isfile(image_path):
- logger.GetLogger().LogError('"{0}" is not a file.'.format(image_path))
- return 1
-
- chromeos_root = None
- if options.chromeos_root:
- chromeos_root = options.chromeos_root
- else:
- ## Deduce chromeos root from image
- t = image_path
- while t != '/':
- if misc.IsChromeOsTree(t):
- break
- t = os.path.dirname(t)
- if misc.IsChromeOsTree(t):
- chromeos_root = t
-
- if not chromeos_root:
+ ' and "/tmp/mount_basename.x.stateful". (x is 1 or 2).'
+ ),
+ )
+ parser.add_argument(
+ "--diff_file",
+ dest="diff_file",
+ default=None,
+ help="Dumping all the diffs (if any) to the diff file",
+ )
+ parser.add_argument(
+ "--image1",
+ dest="image1",
+ default=None,
+ required=True,
+ help=("Image 1 file name."),
+ )
+ parser.add_argument(
+ "--image2",
+ dest="image2",
+ default=None,
+ required=True,
+ help=("Image 2 file name."),
+ )
+ options = parser.parse_args(argv[1:])
+
+ if options.mount_basename and options.mount_basename.find("/") >= 0:
logger.GetLogger().LogError(
- 'Please provide a valid chromeos root via --chromeos_root')
+ '"--mount_basename" must be a name, not a path.'
+ )
+ parser.print_help()
return 1
- image = CrosImage(image_path, chromeos_root, options.no_unmount)
-
- if options.mount_basename:
- mount_basename = '{basename}.{index}'.format(
- basename=options.mount_basename, index=i)
- else:
- mount_basename = None
-
- if image.MountImage(mount_basename):
- images.append(image)
- image.FindElfFiles()
-
- if len(images) == 2:
- image_comparator = ImageComparator(images, options.diff_file)
- result = image_comparator.CompareImages()
- finally:
- for image in images:
- image.UnmountImage()
- if image_comparator:
- image_comparator.Cleanup()
-
- return 0 if result else 1
-
-
-if __name__ == '__main__':
- Main(sys.argv)
+ result = False
+ image_comparator = None
+ try:
+ for i, image_path in enumerate(
+ [options.image1, options.image2], start=1
+ ):
+ image_path = os.path.realpath(image_path)
+ if not os.path.isfile(image_path):
+ logger.GetLogger().LogError(
+ '"{0}" is not a file.'.format(image_path)
+ )
+ return 1
+
+ chromeos_root = None
+ if options.chromeos_root:
+ chromeos_root = options.chromeos_root
+ else:
+ ## Deduce chromeos root from image
+ t = image_path
+ while t != "/":
+ if misc.IsChromeOsTree(t):
+ break
+ t = os.path.dirname(t)
+ if misc.IsChromeOsTree(t):
+ chromeos_root = t
+
+ if not chromeos_root:
+ logger.GetLogger().LogError(
+ "Please provide a valid chromeos root via --chromeos_root"
+ )
+ return 1
+
+ image = CrosImage(image_path, chromeos_root, options.no_unmount)
+
+ if options.mount_basename:
+ mount_basename = "{basename}.{index}".format(
+ basename=options.mount_basename, index=i
+ )
+ else:
+ mount_basename = None
+
+ if image.MountImage(mount_basename):
+ images.append(image)
+ image.FindElfFiles()
+
+ if len(images) == 2:
+ image_comparator = ImageComparator(images, options.diff_file)
+ result = image_comparator.CompareImages()
+ finally:
+ for image in images:
+ image.UnmountImage()
+ if image_comparator:
+ image_comparator.Cleanup()
+
+ return 0 if result else 1
+
+
+if __name__ == "__main__":
+ Main(sys.argv)