|
| 1 | +using System; |
| 2 | +using System.IO; |
| 3 | +using System.Linq; |
| 4 | +using System.Text; |
| 5 | + |
| 6 | +using ELFSharp; |
| 7 | +using ELFSharp.ELF; |
| 8 | +using ELFSharp.ELF.Sections; |
| 9 | + |
| 10 | +namespace Microsoft.Android.AppTools.Native; |
| 11 | + |
| 12 | +abstract class AnELF |
| 13 | +{ |
| 14 | + protected static readonly byte[] EmptyArray = Array.Empty<byte> (); |
| 15 | + |
| 16 | + const string DynsymSectionName = ".dynsym"; |
| 17 | + const string SymtabSectionName = ".symtab"; |
| 18 | + const string RodataSectionName = ".rodata"; |
| 19 | + |
| 20 | + ISymbolTable dynamicSymbolsSection; |
| 21 | + ISection rodataSection; |
| 22 | + ISymbolTable? symbolsSection; |
| 23 | + string filePath; |
| 24 | + IELF elf; |
| 25 | + Stream elfStream; |
| 26 | + |
| 27 | + protected ISymbolTable DynSymSection => dynamicSymbolsSection; |
| 28 | + protected ISymbolTable? SymSection => symbolsSection; |
| 29 | + protected ISection RodataSection => rodataSection; |
| 30 | + public IELF AnyELF => elf; |
| 31 | + protected Stream ELFStream => elfStream; |
| 32 | + protected ILogger Log { get; } |
| 33 | + |
| 34 | + public string FilePath => filePath; |
| 35 | + public int PointerSize => Is64Bit ? 8 : 4; |
| 36 | + |
| 37 | + public abstract bool Is64Bit { get; } |
| 38 | + public abstract string Bitness { get; } |
| 39 | + |
| 40 | + protected AnELF (ILogger log, Stream stream, string filePath, IELF elf, ISymbolTable dynsymSection, ISection rodataSection, ISymbolTable? symSection) |
| 41 | + { |
| 42 | + Log = log; |
| 43 | + this.filePath = filePath; |
| 44 | + this.elf = elf; |
| 45 | + elfStream = stream; |
| 46 | + dynamicSymbolsSection = dynsymSection; |
| 47 | + this.rodataSection = rodataSection; |
| 48 | + symbolsSection = symSection; |
| 49 | + } |
| 50 | + |
| 51 | + public ISymbolEntry? GetSymbol (string symbolName) |
| 52 | + { |
| 53 | + ISymbolEntry? symbol = null; |
| 54 | + |
| 55 | + if (symbolsSection != null) { |
| 56 | + symbol = GetSymbol (symbolsSection, symbolName); |
| 57 | + } |
| 58 | + |
| 59 | + if (symbol == null) { |
| 60 | + symbol = GetSymbol (dynamicSymbolsSection, symbolName); |
| 61 | + } |
| 62 | + |
| 63 | + return symbol; |
| 64 | + } |
| 65 | + |
| 66 | + protected static ISymbolEntry? GetSymbol (ISymbolTable symtab, string symbolName) |
| 67 | + { |
| 68 | + return symtab.Entries.Where (entry => String.Compare (entry.Name, symbolName, StringComparison.Ordinal) == 0).FirstOrDefault (); |
| 69 | + } |
| 70 | + |
| 71 | + protected static SymbolEntry<T>? GetSymbol<T> (SymbolTable<T> symtab, T symbolValue) where T: struct |
| 72 | + { |
| 73 | + return symtab.Entries.Where (entry => entry.Value.Equals (symbolValue)).FirstOrDefault (); |
| 74 | + } |
| 75 | + |
| 76 | + public bool HasSymbol (string symbolName) |
| 77 | + { |
| 78 | + return GetSymbol (symbolName) != null; |
| 79 | + } |
| 80 | + |
| 81 | + public byte[] GetData (string symbolName) |
| 82 | + { |
| 83 | + return GetData (symbolName, out ISymbolEntry? _); |
| 84 | + } |
| 85 | + |
| 86 | + public byte[] GetData (string symbolName, out ISymbolEntry? symbolEntry) |
| 87 | + { |
| 88 | + Log.DebugLine ($"Looking for symbol: {symbolName}"); |
| 89 | + symbolEntry = GetSymbol (symbolName); |
| 90 | + if (symbolEntry == null) |
| 91 | + return EmptyArray; |
| 92 | + |
| 93 | + if (Is64Bit) { |
| 94 | + var symbol64 = symbolEntry as SymbolEntry<ulong>; |
| 95 | + if (symbol64 == null) |
| 96 | + throw new InvalidOperationException ($"Symbol '{symbolName}' is not a valid 64-bit symbol"); |
| 97 | + return GetData (symbol64); |
| 98 | + } |
| 99 | + |
| 100 | + var symbol32 = symbolEntry as SymbolEntry<uint>; |
| 101 | + if (symbol32 == null) |
| 102 | + throw new InvalidOperationException ($"Symbol '{symbolName}' is not a valid 32-bit symbol"); |
| 103 | + |
| 104 | + return GetData (symbol32); |
| 105 | + } |
| 106 | + |
| 107 | + public string? GetStringFromPointer (ISymbolEntry symbolEntry) |
| 108 | + { |
| 109 | + return GetStringFromPointerField (symbolEntry, 0); |
| 110 | + } |
| 111 | + |
| 112 | + public abstract string? GetStringFromPointerField (ISymbolEntry symbolEntry, ulong pointerFieldOffset); |
| 113 | + public abstract byte[] GetData (ulong symbolValue, ulong size); |
| 114 | + |
| 115 | + public string? GetASCIIZ (ulong symbolValue) |
| 116 | + { |
| 117 | + return GetASCIIZ (GetData (symbolValue, 0), 0); |
| 118 | + } |
| 119 | + |
| 120 | + public string? GetASCIIZ (byte[] data, ulong offset) |
| 121 | + { |
| 122 | + if (offset >= (ulong)data.LongLength) { |
| 123 | + Log.DebugLine ("Not enough data to retrieve an ASCIIZ string"); |
| 124 | + return null; |
| 125 | + } |
| 126 | + |
| 127 | + int count = data.Length; |
| 128 | + |
| 129 | + for (ulong i = offset; i < (ulong)data.LongLength; i++) { |
| 130 | + if (data[i] == 0) { |
| 131 | + count = (int)(i - offset); |
| 132 | + break; |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + return Encoding.ASCII.GetString (data, (int)offset, count); |
| 137 | + } |
| 138 | + |
| 139 | + public ulong GetPaddedSize<S> (ulong sizeSoFar) => NativeHelpers.GetPaddedSize<S> (sizeSoFar, Is64Bit); |
| 140 | + |
| 141 | + public ulong GetPaddedSize<S> (ulong sizeSoFar, S _) |
| 142 | + { |
| 143 | + return GetPaddedSize<S> (sizeSoFar); |
| 144 | + } |
| 145 | + |
| 146 | + protected virtual byte[] GetData (SymbolEntry<ulong> symbol) |
| 147 | + { |
| 148 | + throw new NotSupportedException (); |
| 149 | + } |
| 150 | + |
| 151 | + protected virtual byte[] GetData (SymbolEntry<uint> symbol) |
| 152 | + { |
| 153 | + throw new NotSupportedException (); |
| 154 | + } |
| 155 | + |
| 156 | + protected byte[] GetData (ISymbolEntry symbol, ulong size, ulong offset) |
| 157 | + { |
| 158 | + return GetData (symbol.PointedSection, size, offset); |
| 159 | + } |
| 160 | + |
| 161 | + protected byte[] GetData (ISection section, ulong size, ulong offset) |
| 162 | + { |
| 163 | + ulong sectionOffset = (elf.Class == Class.Bit64 ? ((Section<ulong>)section).Offset : ((Section<uint>)section).Offset); |
| 164 | + Log.VerboseLine ($"AnELF.GetData: section == {section.Name}; type == {section.Type}; flags == {section.Flags}; offset into binary == {sectionOffset}; size == {size}"); |
| 165 | + byte[] data = section.GetContents (); |
| 166 | + |
| 167 | + Log.VerboseLine ($" section data length: {data.Length} (long: {data.LongLength})"); |
| 168 | + Log.VerboseLine ($" offset into section: {offset}; symbol data length: {size}"); |
| 169 | + if ((ulong)data.LongLength < (offset + size)) { |
| 170 | + return EmptyArray; |
| 171 | + } |
| 172 | + |
| 173 | + if (size == 0) |
| 174 | + size = (ulong)data.Length - offset; |
| 175 | + |
| 176 | + var ret = new byte[size]; |
| 177 | + checked { |
| 178 | + Array.Copy (data, (int)offset, ret, 0, (int)size); |
| 179 | + } |
| 180 | + |
| 181 | + return ret; |
| 182 | + } |
| 183 | + |
| 184 | + public uint GetUInt32 (string symbolName) |
| 185 | + { |
| 186 | + return GetUInt32 (GetData (symbolName), 0, symbolName); |
| 187 | + } |
| 188 | + |
| 189 | + public uint GetUInt32 (ulong symbolValue) |
| 190 | + { |
| 191 | + return GetUInt32 (GetData (symbolValue, 4), 0, symbolValue.ToString ()); |
| 192 | + } |
| 193 | + |
| 194 | + protected uint GetUInt32 (byte[] data, ulong offset, string symbolName) |
| 195 | + { |
| 196 | + if (data.Length < 4) { |
| 197 | + throw new InvalidOperationException ($"Data not big enough to retrieve a 32-bit integer from it (need 4, got {data.Length})"); |
| 198 | + } |
| 199 | + |
| 200 | + return BitConverter.ToUInt32 (GetIntegerData (4, data, offset, symbolName), 0); |
| 201 | + } |
| 202 | + |
| 203 | + public ulong GetUInt64 (string symbolName) |
| 204 | + { |
| 205 | + return GetUInt64 (GetData (symbolName), 0, symbolName); |
| 206 | + } |
| 207 | + |
| 208 | + public ulong GetUInt64 (ulong symbolValue) |
| 209 | + { |
| 210 | + return GetUInt64 (GetData (symbolValue, 8), 0, symbolValue.ToString ()); |
| 211 | + } |
| 212 | + |
| 213 | + protected ulong GetUInt64 (byte[] data, ulong offset, string symbolName) |
| 214 | + { |
| 215 | + if (data.Length < 8) |
| 216 | + throw new InvalidOperationException ("Data not big enough to retrieve a 64-bit integer from it"); |
| 217 | + return BitConverter.ToUInt64 (GetIntegerData (8, data, offset, symbolName), 0); |
| 218 | + } |
| 219 | + |
| 220 | + byte[] GetIntegerData (uint size, byte[] data, ulong offset, string symbolName) |
| 221 | + { |
| 222 | + if ((ulong)data.LongLength < (offset + size)) { |
| 223 | + string bits = size == 4 ? "32" : "64"; |
| 224 | + throw new InvalidOperationException ($"Unable to read UInt{bits} value for symbol '{symbolName}': data not long enough"); |
| 225 | + } |
| 226 | + |
| 227 | + byte[] ret = new byte[size]; |
| 228 | + Array.Copy (data, (int)offset, ret, 0, ret.Length); |
| 229 | + Endianess myEndianness = BitConverter.IsLittleEndian ? Endianess.LittleEndian : Endianess.BigEndian; |
| 230 | + if (AnyELF.Endianess != myEndianness) { |
| 231 | + Array.Reverse (ret); |
| 232 | + } |
| 233 | + |
| 234 | + return ret; |
| 235 | + } |
| 236 | + |
| 237 | + public static bool TryLoad (ILogger log, string filePath, out AnELF? anElf) |
| 238 | + { |
| 239 | + using var fs = File.OpenRead (filePath); |
| 240 | + return TryLoad (log, fs, filePath, out anElf); |
| 241 | + } |
| 242 | + |
| 243 | + public static bool TryLoad (ILogger log, Stream stream, string filePath, out AnELF? anElf) |
| 244 | + { |
| 245 | + anElf = null; |
| 246 | + Class elfClass = ELFReader.CheckELFType (stream); |
| 247 | + if (elfClass == Class.NotELF) { |
| 248 | + log.WarningLine ($"AnELF.TryLoad: {filePath} is not an ELF binary"); |
| 249 | + return false; |
| 250 | + } |
| 251 | + |
| 252 | + IELF elf = ELFReader.Load (stream, shouldOwnStream: false); |
| 253 | + |
| 254 | + if (elf.Type != FileType.SharedObject) { |
| 255 | + log.WarningLine ($"AnELF.TryLoad: {filePath} is not a shared library"); |
| 256 | + return false; |
| 257 | + } |
| 258 | + |
| 259 | + if (elf.Endianess != Endianess.LittleEndian) { |
| 260 | + log.WarningLine ($"AnELF.TryLoad: {filePath} is not a little-endian binary"); |
| 261 | + return false; |
| 262 | + } |
| 263 | + |
| 264 | + bool is64; |
| 265 | + switch (elf.Machine) { |
| 266 | + case Machine.ARM: |
| 267 | + case Machine.Intel386: |
| 268 | + is64 = false; |
| 269 | + |
| 270 | + break; |
| 271 | + |
| 272 | + case Machine.AArch64: |
| 273 | + case Machine.AMD64: |
| 274 | + is64 = true; |
| 275 | + |
| 276 | + break; |
| 277 | + |
| 278 | + default: |
| 279 | + log.WarningLine ($"{filePath} is for an unsupported machine type {elf.Machine}"); |
| 280 | + return false; |
| 281 | + } |
| 282 | + |
| 283 | + ISymbolTable? symtab = GetSymbolTable (elf, DynsymSectionName); |
| 284 | + if (symtab == null) { |
| 285 | + log.WarningLine ($"{filePath} does not contain dynamic symbol section '{DynsymSectionName}'"); |
| 286 | + return false; |
| 287 | + } |
| 288 | + ISymbolTable dynsym = symtab; |
| 289 | + |
| 290 | + ISection? sec = GetSection (elf, RodataSectionName); |
| 291 | + if (sec == null) { |
| 292 | + log.WarningLine ("${filePath} does not contain read-only data section ('{RodataSectionName}')"); |
| 293 | + return false; |
| 294 | + } |
| 295 | + ISection rodata = sec; |
| 296 | + |
| 297 | + ISymbolTable? sym = GetSymbolTable (elf, SymtabSectionName); |
| 298 | + |
| 299 | + if (is64) { |
| 300 | + anElf = new ELF64 (log, stream, filePath, elf, dynsym, rodata, sym); |
| 301 | + } else { |
| 302 | + anElf = new ELF32 (log, stream, filePath, elf, dynsym, rodata, sym); |
| 303 | + } |
| 304 | + |
| 305 | + log.DebugLine ($"AnELF.TryLoad: {filePath} is a {anElf.Bitness}-bit ELF binary ({elf.Machine})"); |
| 306 | + return true; |
| 307 | + } |
| 308 | + |
| 309 | + protected static ISymbolTable? GetSymbolTable (IELF elf, string sectionName) |
| 310 | + { |
| 311 | + ISection? section = GetSection (elf, sectionName); |
| 312 | + if (section == null) { |
| 313 | + return null; |
| 314 | + } |
| 315 | + |
| 316 | + var symtab = section as ISymbolTable; |
| 317 | + if (symtab == null) { |
| 318 | + return null; |
| 319 | + } |
| 320 | + |
| 321 | + return symtab; |
| 322 | + } |
| 323 | + |
| 324 | + protected static ISection? GetSection (IELF elf, string sectionName) |
| 325 | + { |
| 326 | + if (!elf.TryGetSection (sectionName, out ISection section)) { |
| 327 | + return null; |
| 328 | + } |
| 329 | + |
| 330 | + return section; |
| 331 | + } |
| 332 | +} |
0 commit comments