1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.build.tests; 18 19 import static org.junit.Assert.assertEquals; 20 21 import com.android.tradefed.build.IBuildInfo; 22 import com.android.tradefed.config.Option; 23 import com.android.tradefed.config.OptionClass; 24 import com.android.tradefed.log.LogUtil.CLog; 25 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 26 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 27 import com.android.tradefed.util.CommandResult; 28 import com.android.tradefed.util.CommandStatus; 29 import com.android.tradefed.util.FileUtil; 30 import com.android.tradefed.util.RunUtil; 31 32 import org.junit.Before; 33 import org.junit.Test; 34 import org.junit.runner.RunWith; 35 36 import java.io.File; 37 import java.io.IOException; 38 import java.util.ArrayList; 39 import java.util.stream.Stream; 40 41 /** A device-less test that test kernel image */ 42 @OptionClass(alias = "kernel-image-check") 43 @RunWith(DeviceJUnit4ClassRunner.class) 44 public class KernelImageCheck extends BaseHostJUnit4Test { 45 46 private static final String KERNEL_IMAGE_NAME = "vmlinux"; 47 private static final String KERNEL_IMAGE_REPO = "common"; 48 private static final int CMD_TIMEOUT = 1000000; 49 50 @Option( 51 name = "kernel-image-check-tool", 52 description = "The file path of kernel image check tool (mandatory)", 53 mandatory = true 54 ) 55 private File mKernelImageCheckTool = null; 56 57 @Option( 58 name = "kernel-image-name", 59 description = "The file name of the kernel image. Default: vmlinux" 60 ) 61 private String mKernelImageName = KERNEL_IMAGE_NAME; 62 63 @Option( 64 name = "kernel-image-alt-path", 65 description = "The kernel image alternative path string" 66 ) 67 private String mKernelImageAltPath = null; 68 69 @Option( 70 name = "kernel-abi-file", 71 description = "The file path of kernel ABI file" 72 ) 73 private File mKernelAbiFile = null; 74 75 private IBuildInfo mBuildInfo; 76 private File mKernelImageFile = null; 77 private File mKernelAbiWhitelist = null; 78 79 @Before setUp()80 public void setUp() throws Exception { 81 if (mKernelImageCheckTool == null || !mKernelImageCheckTool.exists()) { 82 throw new IOException("Cannot find kernel image tool at: " + mKernelImageCheckTool); 83 } 84 85 // If --kernel-abi-file has not been specified, try to find 'abi.xml' 86 // within the downloaded files from the build. 87 if (mKernelAbiFile == null) { 88 mKernelAbiFile = getBuild().getFile("abi.xml"); 89 } 90 91 // If there was not any 'abi.xml' and --kernel-abi-file was not set to 92 // point at an external one, throw that. 93 if (mKernelAbiFile == null) { 94 throw new IOException("Cannot find abi.xml within the build results."); 95 } 96 97 if (!mKernelAbiFile.exists()) { 98 throw new IOException("Cannot find kernel ABI representation at: " + mKernelAbiFile); 99 } 100 // First try to get kernel image from BuildInfo 101 mKernelImageFile = getBuild().getFile(mKernelImageName); 102 if ((mKernelImageFile == null || !mKernelImageFile.exists()) 103 && mKernelImageAltPath != null) { 104 // Then check within alternative path. 105 File imageDir = new File(mKernelImageAltPath); 106 if (imageDir.isDirectory()) { 107 mKernelImageFile = new File(imageDir, mKernelImageName); 108 } 109 } 110 111 if (mKernelImageFile == null || !mKernelImageFile.exists()) { 112 throw new RuntimeException("Cannot find kernel image file: " + mKernelImageName); 113 } 114 115 // This is an optional parameter. No need to check for an error here. 116 mKernelAbiWhitelist = getBuild().getFile("abi_whitelist"); 117 if ((mKernelAbiWhitelist == null || !mKernelAbiWhitelist.exists()) 118 && mKernelImageAltPath != null) { 119 // Then check within alternative path. 120 File imageDir = new File(mKernelImageAltPath); 121 if (imageDir.isDirectory()) { 122 mKernelAbiWhitelist = new File(imageDir, "abi_whitelist"); 123 } 124 } 125 } 126 127 /** Test that kernel ABI is not different from the given ABI representation */ 128 @Test test_stable_abi()129 public void test_stable_abi() throws Exception { 130 // Figure out location of Linux source tree by inspecting vmlinux. 131 String[] cmd = new String[] {"strings", mKernelImageFile.getPath(), "-d"}; 132 CommandResult result = RunUtil.getDefault().runTimedCmd(CMD_TIMEOUT, cmd); 133 assertEquals(CommandStatus.SUCCESS, result.getStatus()); 134 135 String vmlinuxStrings = result.getStdout(); 136 File vmlinuxStringsFile = FileUtil.createTempFile("vmlinuxStrings", ".txt"); 137 FileUtil.writeToFile(vmlinuxStrings, vmlinuxStringsFile); 138 139 cmd = new String[] {"grep", "-m", "1", "init/main.c", vmlinuxStringsFile.getAbsolutePath()}; 140 result = RunUtil.getDefault().runTimedCmd(CMD_TIMEOUT, cmd); 141 assertEquals(CommandStatus.SUCCESS, result.getStatus()); 142 143 String repoRootDir = result.getStdout(); 144 // Source file absolute path is /repoRootDir/KERNEL_IMAGE_REPO/location-in-linux-tree. 145 repoRootDir = repoRootDir.split("/" + KERNEL_IMAGE_REPO + "/", 2)[0]; 146 147 // Try to set the executable bit for abidw and abidiff 148 Stream.of("abidw", "abidiff") 149 .forEach( 150 bin -> { 151 String[] chmod = 152 new String[] { 153 "chmod", 154 "u+x", 155 mKernelImageCheckTool.getAbsolutePath() + "/" + bin 156 }; 157 RunUtil.getDefault().runTimedCmd(CMD_TIMEOUT, chmod); 158 }); 159 160 // Generate kernel ABI 161 ArrayList<String> abidwCmd = new ArrayList<String>(); 162 abidwCmd.add(mKernelImageCheckTool.getAbsolutePath() + "/abidw"); 163 // omit various sources of indeterministic abidw output 164 abidwCmd.add("--no-corpus-path"); 165 abidwCmd.add("--no-comp-dir-path"); 166 // the path containing vmlinux and *.ko 167 abidwCmd.add("--linux-tree"); 168 abidwCmd.add(mKernelImageFile.getParent()); 169 abidwCmd.add("--out-file"); 170 abidwCmd.add("abi-new.xml"); 171 if (mKernelAbiWhitelist != null && mKernelAbiWhitelist.exists()) { 172 abidwCmd.add("--kmi-whitelist"); 173 abidwCmd.add(mKernelAbiWhitelist.getAbsolutePath()); 174 } 175 result = RunUtil.getDefault().runTimedCmd(CMD_TIMEOUT, abidwCmd.toArray(new String[0])); 176 CLog.i("Result stdout: %s", result.getStdout()); 177 // TODO: differentiate non-zero exit codes. 178 if (result.getExitCode() != 0) { 179 CLog.e("Result stderr: %s", result.getStderr()); 180 CLog.e("Result exit code: %d", result.getExitCode()); 181 } 182 assertEquals(CommandStatus.SUCCESS, result.getStatus()); 183 184 // Sanitize abi-new.xml by removing any occurences of the kernel path. 185 cmd = 186 new String[] { 187 "sed", 188 "-i", 189 "s#" + repoRootDir + "/" + KERNEL_IMAGE_REPO + "/##g", 190 "abi-new.xml" 191 }; 192 result = RunUtil.getDefault().runTimedCmd(CMD_TIMEOUT, cmd); 193 assertEquals(CommandStatus.SUCCESS, result.getStatus()); 194 195 cmd = new String[] {"sed", "-i", "s#" + repoRootDir + "/##g", "abi-new.xml"}; 196 result = RunUtil.getDefault().runTimedCmd(CMD_TIMEOUT, cmd); 197 assertEquals(CommandStatus.SUCCESS, result.getStatus()); 198 199 // Diff kernel ABI with the given ABI file 200 ArrayList<String> abidiffCmd = new ArrayList<String>(); 201 abidiffCmd.add(mKernelImageCheckTool.getAbsolutePath() + "/abidiff"); 202 abidiffCmd.add("--impacted-interfaces"); 203 abidiffCmd.add("--leaf-changes-only"); 204 abidiffCmd.add("--dump-diff-tree"); 205 abidiffCmd.add(mKernelAbiFile.getAbsolutePath()); 206 abidiffCmd.add("abi-new.xml"); 207 if (mKernelAbiWhitelist != null && mKernelAbiWhitelist.exists()) { 208 abidiffCmd.add("--kmi-whitelist"); 209 abidiffCmd.add(mKernelAbiWhitelist.getAbsolutePath()); 210 } 211 result = RunUtil.getDefault().runTimedCmd(CMD_TIMEOUT, abidiffCmd.toArray(new String[0])); 212 CLog.i("Result stdout: %s", result.getStdout()); 213 if (result.getExitCode() != 0) { 214 CLog.e("Result stderr: %s", result.getStderr()); 215 CLog.e("Result exit code: %d", result.getExitCode()); 216 } 217 assertEquals( 218 "Kernel's ABI has changed. See go/kernel-abi-monitoring for details.\n", 219 CommandStatus.SUCCESS, 220 result.getStatus()); 221 } 222 } 223