1 /* 2 * Copyright (C) 2018 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.compatibility.common.util; 18 19 import org.junit.Test; 20 import org.junit.runner.RunWith; 21 import org.junit.runners.JUnit4; 22 23 import java.io.BufferedReader; 24 import java.io.File; 25 import java.io.FileOutputStream; 26 import java.io.FileReader; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.OutputStream; 30 import java.util.ArrayList; 31 import java.util.List; 32 33 import static org.junit.Assert.*; 34 35 /** 36 * Tests if {@link ReadElf} parses Executable and Linkable Format files properly. 37 * 38 * <p>These tests validate content, parsed by {@link ReadElf} is the same with the golden sample 39 * files. The golden sample files are the outputs from Linux cmd: readelf -a elf-file 40 * 41 * <p> 42 */ 43 @RunWith(JUnit4.class) 44 public class ReadElfTest { 45 private static final String THIS_CLASS = "com.android.compatibility.common.util.ReadElfTest"; 46 private static final String TEST_SO_ARM32B = "arm32_libdl.so"; 47 private static final String TEST_SO_ARM32B_READELF = "arm32_libdl.txt"; 48 private static final String TEST_SO_ARM64B = "arm64_libdl.so"; 49 private static final String TEST_SO_ARM64B_READELF = "arm64_libdl.txt"; 50 private static final String TEST_EXE_X8664B = "x86app_process64"; 51 private static final String TEST_EXE_X8664B_READELF = "x86app_process64.txt"; 52 private static final String TEST_EXE_X8632B = "x86app_process32"; 53 private static final String TEST_EXE_X8632B_READELF = "x86app_process32.txt"; 54 55 /** 56 * Test {@link ReadElf} for an ARM 32-bit Shared Object 57 * 58 * @throws Exception 59 */ 60 @Test testReadElfArm32b()61 public void testReadElfArm32b() throws Exception { 62 checkReadElf(TEST_SO_ARM32B, TEST_SO_ARM32B_READELF, ReadElf.ARCH_ARM, 32, ReadElf.ET_DYN); 63 } 64 65 /** 66 * Test {@link ReadElf} for an ARM 64-bit Shared Object 67 * 68 * @throws Exception 69 */ 70 @Test testReadElfArm64b()71 public void testReadElfArm64b() throws Exception { 72 checkReadElf(TEST_SO_ARM64B, TEST_SO_ARM64B_READELF, ReadElf.ARCH_ARM, 64, ReadElf.ET_DYN); 73 } 74 75 /** 76 * Test {@link ReadElf} for an x86 32-bit Executable 77 * 78 * @throws Exception 79 */ 80 @Test testReadElfX8632b()81 public void testReadElfX8632b() throws Exception { 82 checkReadElf( 83 TEST_EXE_X8632B, TEST_EXE_X8632B_READELF, ReadElf.ARCH_X86, 32, ReadElf.ET_DYN); 84 } 85 86 /** 87 * Test {@link ReadElf} for an x86 64-bit Executable 88 * 89 * @throws Exception 90 */ 91 @Test testReadElfX8664b()92 public void testReadElfX8664b() throws Exception { 93 checkReadElf( 94 TEST_EXE_X8664B, TEST_EXE_X8664B_READELF, ReadElf.ARCH_X86, 64, ReadElf.ET_DYN); 95 } 96 97 /** 98 * Compares {@link ReadElf} returns same results with Linux readelf cmd on the same ELF file 99 * 100 * @param elfFileName the name of an ELF file in Resource to be checked 101 * @param elfOutputFileName the name of the golden sample file in Resource 102 * @param bits the expected bits of the ELF file 103 * @param arch the expected Instruction Set Architecture of the ELF file 104 * @param type the expected object file type of the ELF file 105 */ checkReadElf( String elfFileName, String elfOutputFileName, String arch, int bits, int type)106 private void checkReadElf( 107 String elfFileName, String elfOutputFileName, String arch, int bits, int type) 108 throws Exception { 109 File targetFile = getResrouceFile(elfFileName); 110 assertEquals("ReadElf.isElf() " + elfFileName, true, ReadElf.isElf(targetFile)); 111 ReadElf elf = ReadElf.read(targetFile); 112 assertEquals("getBits() ", bits, elf.getBits()); 113 assertEquals("getArchitecture() ", arch, elf.getArchitecture()); 114 assertEquals("isDynamic() ", true, elf.isDynamic()); 115 assertEquals("getType() ", type, elf.getType()); 116 117 File elfOutputFile = getResrouceFile(elfOutputFileName); 118 assertEquals("ReadElf.isElf() " + elfOutputFileName, false, ReadElf.isElf(elfOutputFile)); 119 120 final ReadElf.Symbol[] dynSymbolArr = elf.getDynSymArr(); 121 chkDynSymbol(elfOutputFile, dynSymbolArr); 122 123 assertEquals( 124 "ReadElf.getDynamicDependencies() " + elfFileName, 125 getDynamicDependencies(elfOutputFile), 126 elf.getDynamicDependencies()); 127 128 assertEquals( 129 "ReadElf.getRoStrings() " + elfFileName, 130 getRoStrings(elfOutputFile), 131 elf.getRoStrings()); 132 } 133 134 /** 135 * Gets a list of needed libraries from a Linux readelf cmd output file 136 * 137 * @param elfOutputFileName a {@link File} object of an golden sample file 138 * @return a name list of needed libraries 139 */ getDynamicDependencies(File elfOutputFile)140 private List<String> getDynamicDependencies(File elfOutputFile) throws IOException { 141 List<String> result = new ArrayList<>(); 142 143 FileReader fileReader = new FileReader(elfOutputFile); 144 BufferedReader buffReader = new BufferedReader(fileReader); 145 146 String line; 147 boolean keepGoing = true; 148 while ((line = buffReader.readLine()) != null && keepGoing) { 149 // readelf output as: Dynamic section at offset 0xfdf0 contains 17 entries: 150 if (line.startsWith("Dynamic section")) { 151 String dsLine; 152 while ((dsLine = buffReader.readLine()) != null) { 153 String trimLine = dsLine.trim(); 154 if (trimLine.isEmpty()) { 155 // End of the block 156 keepGoing = false; 157 break; 158 } 159 160 // 0x0000000000000001 (NEEDED) Shared library: [ld-android.so] 161 if (trimLine.contains("1 (NEEDED)")) { 162 result.add( 163 trimLine.substring( 164 trimLine.indexOf("[") + 1, trimLine.indexOf("]"))); 165 } 166 } 167 } 168 } 169 fileReader.close(); 170 return result; 171 } 172 173 /** 174 * Checks if all Dynamic Symbols in a golden sample file are in the symbol array 175 * 176 * @param targetFile a {@link File} object of an golden sample file 177 * @param dynSymbolArr a Dynamic Symbol array to be validated 178 */ chkDynSymbol(File targetFile, ReadElf.Symbol[] dynSymbolArr)179 private void chkDynSymbol(File targetFile, ReadElf.Symbol[] dynSymbolArr) throws IOException { 180 FileReader fileReader = new FileReader(targetFile); 181 BufferedReader buffReader = new BufferedReader(fileReader); 182 183 String line; 184 boolean keepGoing = true; 185 while ((line = buffReader.readLine()) != null && keepGoing) { 186 // readelf output as: Symbol table '.dynsym' contains 44 entries: 187 if (line.startsWith("Symbol table '.dynsym'")) { 188 // Skip the header: Num: Value Size Type Bind Vis Ndx Name 189 buffReader.readLine(); 190 // Skip the 1st line: 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 191 buffReader.readLine(); 192 193 int i = 1; 194 195 String dsLine; 196 while ((dsLine = buffReader.readLine()) != null) { 197 // readelf output as: 198 // 20: 0000000000000ff8 24 FUNC WEAK DEFAULT 9 199 // android_init_anonymous_na@@LIBC_PLATFORM 200 String trimLine = dsLine.trim(); 201 if (trimLine.isEmpty()) { 202 // End of the block 203 keepGoing = false; 204 break; 205 } 206 207 // Removes tailing (x) for an executable 208 // 1: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@LIBC (2) 209 int idx = trimLine.indexOf("("); 210 if (idx > 0) { 211 trimLine = trimLine.substring(0, idx); 212 } 213 214 String phases[] = trimLine.split("\\s+"); 215 String symName = dynSymbolArr[i].name; 216 String name = phases[phases.length - 1].split("@")[0]; 217 // readelf may truncate a long name 218 assertTrue( 219 String.format("chkDynSymbol name %d: %s vs %s", i, symName, name), 220 symName.startsWith(name)); 221 assertEquals("chkDynSymbol type :", dynSymbolArr[i].toType(), phases[3]); 222 assertEquals("chkDynSymbol bind :", dynSymbolArr[i].toBind(), phases[4]); 223 assertEquals("chkDynSymbol ndx :", dynSymbolArr[i].toShndx(), phases[6]); 224 i++; 225 } 226 } 227 } 228 fileReader.close(); 229 } 230 231 /** 232 * Gets a list of Read Only Strings from a Linux readelf -p .rodata cmd output file 233 * 234 * @param elfOutputFileName a {@link File} object of an golden sample file 235 * @return a list of RO Strings 236 */ getRoStrings(File elfOutputFile)237 private List<String> getRoStrings(File elfOutputFile) throws IOException { 238 List<String> result = new ArrayList<>(); 239 240 FileReader fileReader = new FileReader(elfOutputFile); 241 BufferedReader buffReader = new BufferedReader(fileReader); 242 243 String line; 244 boolean keepGoing = true; 245 while ((line = buffReader.readLine()) != null && keepGoing) { 246 // readelf output as: String dump of section '.rodata': 247 if (line.startsWith("String dump of section '.rodata':")) { 248 String dsLine; 249 while ((dsLine = buffReader.readLine()) != null) { 250 String trimLine = dsLine.trim(); 251 if (trimLine.isEmpty()) { 252 // End of the block 253 keepGoing = false; 254 break; 255 } 256 257 // [ 108] Error: no class name or --zygote supplied.^J 258 if (trimLine.contains("[")) { 259 result.add( 260 trimLine.substring(trimLine.indexOf("]") + 1, trimLine.length()) 261 .trim() 262 .replace("^J", "\n")); 263 } 264 } 265 } 266 } 267 fileReader.close(); 268 return result; 269 } 270 271 /** 272 * Reads a file from Resource and write to a tmp file 273 * 274 * @param fileName the name of a file in Resource 275 * @return the File object of a tmp file 276 */ getResrouceFile(String fileName)277 private File getResrouceFile(String fileName) throws IOException { 278 File tempFile = File.createTempFile(fileName, "tmp"); 279 tempFile.deleteOnExit(); 280 try (InputStream input = openResourceAsStream(fileName); 281 OutputStream output = new FileOutputStream(tempFile)) { 282 byte[] buffer = new byte[4096]; 283 int length; 284 while ((length = input.read(buffer)) > 0) { 285 output.write(buffer, 0, length); 286 } 287 } 288 return tempFile; 289 } 290 291 /** 292 * Gets an InputStrem of a file from Resource 293 * 294 * @param fileName the name of a file in Resrouce 295 * @return the (@link InputStream} object of the file 296 */ openResourceAsStream(String fileName)297 private InputStream openResourceAsStream(String fileName) { 298 InputStream input = getClass().getResourceAsStream("/" + fileName); 299 assertNotNull(input); 300 return input; 301 } 302 }