aboutsummaryrefslogtreecommitdiff
path: root/chromiumos_image_diff.py
diff options
context:
space:
mode:
Diffstat (limited to 'chromiumos_image_diff.py')
-rwxr-xr-xchromiumos_image_diff.py333
1 files changed, 333 insertions, 0 deletions
diff --git a/chromiumos_image_diff.py b/chromiumos_image_diff.py
new file mode 100755
index 00000000..68791ac5
--- /dev/null
+++ b/chromiumos_image_diff.py
@@ -0,0 +1,333 @@
+#!/usr/bin/python2
+"""Diff 2 chromiumos images by comparing each elf file.
+
+ The script diffs every *ELF* files by dissembling every *executable*
+ section, which means it is not a FULL elf differ.
+
+ A simple usage example -
+ chromiumos_image_diff.py --image1 image-path-1 --image2 image-path-2
+
+ Note that image path should be inside the chroot, if not (ie, image is
+ downloaded from web), please specify a chromiumos checkout via
+ "--chromeos_root".
+
+ And this script should be executed outside chroot.
+"""
+
+from __future__ import print_function
+
+__author__ = 'shenhan@google.com (Han Shen)'
+
+import argparse
+import os
+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
+
+
+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.chromeos_root, 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')
+ 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 = set([e.replace(t1, '') for e in i1.elf_files])
+ t2 = i2.rootfs + '/'
+ elfset2 = set([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 '
+ '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:
+ 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)