1 //===- InputChunks.cpp ----------------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "InputChunks.h" 10 #include "Config.h" 11 #include "OutputSegment.h" 12 #include "WriterUtils.h" 13 #include "lld/Common/ErrorHandler.h" 14 #include "lld/Common/LLVM.h" 15 #include "llvm/Support/LEB128.h" 16 17 #define DEBUG_TYPE "lld" 18 19 using namespace llvm; 20 using namespace llvm::wasm; 21 using namespace llvm::support::endian; 22 23 namespace lld { 24 StringRef relocTypeToString(uint8_t relocType) { 25 switch (relocType) { 26 #define WASM_RELOC(NAME, REL) \ 27 case REL: \ 28 return #NAME; 29 #include "llvm/BinaryFormat/WasmRelocs.def" 30 #undef WASM_RELOC 31 } 32 llvm_unreachable("unknown reloc type"); 33 } 34 35 bool relocIs64(uint8_t relocType) { 36 switch (relocType) { 37 case R_WASM_MEMORY_ADDR_LEB64: 38 case R_WASM_MEMORY_ADDR_SLEB64: 39 case R_WASM_MEMORY_ADDR_REL_SLEB64: 40 case R_WASM_MEMORY_ADDR_I64: 41 return true; 42 default: 43 return false; 44 } 45 } 46 47 std::string toString(const wasm::InputChunk *c) { 48 return (toString(c->file) + ":(" + c->getName() + ")").str(); 49 } 50 51 namespace wasm { 52 StringRef InputChunk::getComdatName() const { 53 uint32_t index = getComdat(); 54 if (index == UINT32_MAX) 55 return StringRef(); 56 return file->getWasmObj()->linkingData().Comdats[index]; 57 } 58 59 void InputChunk::verifyRelocTargets() const { 60 for (const WasmRelocation &rel : relocations) { 61 uint64_t existingValue; 62 unsigned bytesRead = 0; 63 unsigned paddedLEBWidth = 5; 64 auto offset = rel.Offset - getInputSectionOffset(); 65 const uint8_t *loc = data().data() + offset; 66 switch (rel.Type) { 67 case R_WASM_TYPE_INDEX_LEB: 68 case R_WASM_FUNCTION_INDEX_LEB: 69 case R_WASM_GLOBAL_INDEX_LEB: 70 case R_WASM_EVENT_INDEX_LEB: 71 case R_WASM_MEMORY_ADDR_LEB: 72 existingValue = decodeULEB128(loc, &bytesRead); 73 break; 74 case R_WASM_MEMORY_ADDR_LEB64: 75 existingValue = decodeULEB128(loc, &bytesRead); 76 paddedLEBWidth = 10; 77 break; 78 case R_WASM_TABLE_INDEX_SLEB: 79 case R_WASM_TABLE_INDEX_REL_SLEB: 80 case R_WASM_MEMORY_ADDR_SLEB: 81 case R_WASM_MEMORY_ADDR_REL_SLEB: 82 case R_WASM_MEMORY_ADDR_TLS_SLEB: 83 existingValue = static_cast<uint64_t>(decodeSLEB128(loc, &bytesRead)); 84 break; 85 case R_WASM_TABLE_INDEX_SLEB64: 86 case R_WASM_MEMORY_ADDR_SLEB64: 87 case R_WASM_MEMORY_ADDR_REL_SLEB64: 88 existingValue = static_cast<uint64_t>(decodeSLEB128(loc, &bytesRead)); 89 paddedLEBWidth = 10; 90 break; 91 case R_WASM_TABLE_INDEX_I32: 92 case R_WASM_MEMORY_ADDR_I32: 93 case R_WASM_FUNCTION_OFFSET_I32: 94 case R_WASM_SECTION_OFFSET_I32: 95 case R_WASM_GLOBAL_INDEX_I32: 96 existingValue = read32le(loc); 97 break; 98 case R_WASM_TABLE_INDEX_I64: 99 case R_WASM_MEMORY_ADDR_I64: 100 case R_WASM_FUNCTION_OFFSET_I64: 101 existingValue = read64le(loc); 102 break; 103 default: 104 llvm_unreachable("unknown relocation type"); 105 } 106 107 if (bytesRead && bytesRead != paddedLEBWidth) 108 warn("expected LEB at relocation site be 5/10-byte padded"); 109 110 if (rel.Type != R_WASM_GLOBAL_INDEX_LEB && 111 rel.Type != R_WASM_GLOBAL_INDEX_I32) { 112 auto expectedValue = file->calcExpectedValue(rel); 113 if (expectedValue != existingValue) 114 warn(toString(this) + ": unexpected existing value for " + 115 relocTypeToString(rel.Type) + ": existing=" + 116 Twine(existingValue) + " expected=" + Twine(expectedValue)); 117 } 118 } 119 } 120 121 // Copy this input chunk to an mmap'ed output file and apply relocations. 122 void InputChunk::writeTo(uint8_t *buf) const { 123 // Copy contents 124 memcpy(buf + outputOffset, data().data(), data().size()); 125 126 // Apply relocations 127 if (relocations.empty()) 128 return; 129 130 #ifndef NDEBUG 131 verifyRelocTargets(); 132 #endif 133 134 LLVM_DEBUG(dbgs() << "applying relocations: " << toString(this) 135 << " count=" << relocations.size() << "\n"); 136 int32_t off = outputOffset - getInputSectionOffset(); 137 138 for (const WasmRelocation &rel : relocations) { 139 uint8_t *loc = buf + rel.Offset + off; 140 auto value = file->calcNewValue(rel); 141 LLVM_DEBUG(dbgs() << "apply reloc: type=" << relocTypeToString(rel.Type)); 142 if (rel.Type != R_WASM_TYPE_INDEX_LEB) 143 LLVM_DEBUG(dbgs() << " sym=" << file->getSymbols()[rel.Index]->getName()); 144 LLVM_DEBUG(dbgs() << " addend=" << rel.Addend << " index=" << rel.Index 145 << " value=" << value << " offset=" << rel.Offset 146 << "\n"); 147 148 switch (rel.Type) { 149 case R_WASM_TYPE_INDEX_LEB: 150 case R_WASM_FUNCTION_INDEX_LEB: 151 case R_WASM_GLOBAL_INDEX_LEB: 152 case R_WASM_EVENT_INDEX_LEB: 153 case R_WASM_MEMORY_ADDR_LEB: 154 encodeULEB128(value, loc, 5); 155 break; 156 case R_WASM_MEMORY_ADDR_LEB64: 157 encodeULEB128(value, loc, 10); 158 break; 159 case R_WASM_TABLE_INDEX_SLEB: 160 case R_WASM_TABLE_INDEX_REL_SLEB: 161 case R_WASM_MEMORY_ADDR_SLEB: 162 case R_WASM_MEMORY_ADDR_REL_SLEB: 163 case R_WASM_MEMORY_ADDR_TLS_SLEB: 164 encodeSLEB128(static_cast<int32_t>(value), loc, 5); 165 break; 166 case R_WASM_TABLE_INDEX_SLEB64: 167 case R_WASM_MEMORY_ADDR_SLEB64: 168 case R_WASM_MEMORY_ADDR_REL_SLEB64: 169 encodeSLEB128(static_cast<int64_t>(value), loc, 10); 170 break; 171 case R_WASM_TABLE_INDEX_I32: 172 case R_WASM_MEMORY_ADDR_I32: 173 case R_WASM_FUNCTION_OFFSET_I32: 174 case R_WASM_SECTION_OFFSET_I32: 175 case R_WASM_GLOBAL_INDEX_I32: 176 write32le(loc, value); 177 break; 178 case R_WASM_TABLE_INDEX_I64: 179 case R_WASM_MEMORY_ADDR_I64: 180 case R_WASM_FUNCTION_OFFSET_I64: 181 write64le(loc, value); 182 break; 183 default: 184 llvm_unreachable("unknown relocation type"); 185 } 186 } 187 } 188 189 // Copy relocation entries to a given output stream. 190 // This function is used only when a user passes "-r". For a regular link, 191 // we consume relocations instead of copying them to an output file. 192 void InputChunk::writeRelocations(raw_ostream &os) const { 193 if (relocations.empty()) 194 return; 195 196 int32_t off = outputOffset - getInputSectionOffset(); 197 LLVM_DEBUG(dbgs() << "writeRelocations: " << file->getName() 198 << " offset=" << Twine(off) << "\n"); 199 200 for (const WasmRelocation &rel : relocations) { 201 writeUleb128(os, rel.Type, "reloc type"); 202 writeUleb128(os, rel.Offset + off, "reloc offset"); 203 writeUleb128(os, file->calcNewIndex(rel), "reloc index"); 204 205 if (relocTypeHasAddend(rel.Type)) 206 writeSleb128(os, file->calcNewAddend(rel), "reloc addend"); 207 } 208 } 209 210 void InputFunction::setFunctionIndex(uint32_t index) { 211 LLVM_DEBUG(dbgs() << "InputFunction::setFunctionIndex: " << getName() 212 << " -> " << index << "\n"); 213 assert(!hasFunctionIndex()); 214 functionIndex = index; 215 } 216 217 void InputFunction::setTableIndex(uint32_t index) { 218 LLVM_DEBUG(dbgs() << "InputFunction::setTableIndex: " << getName() << " -> " 219 << index << "\n"); 220 assert(!hasTableIndex()); 221 tableIndex = index; 222 } 223 224 // Write a relocation value without padding and return the number of bytes 225 // witten. 226 static unsigned writeCompressedReloc(uint8_t *buf, const WasmRelocation &rel, 227 uint64_t value) { 228 switch (rel.Type) { 229 case R_WASM_TYPE_INDEX_LEB: 230 case R_WASM_FUNCTION_INDEX_LEB: 231 case R_WASM_GLOBAL_INDEX_LEB: 232 case R_WASM_EVENT_INDEX_LEB: 233 case R_WASM_MEMORY_ADDR_LEB: 234 case R_WASM_MEMORY_ADDR_LEB64: 235 return encodeULEB128(value, buf); 236 case R_WASM_TABLE_INDEX_SLEB: 237 case R_WASM_TABLE_INDEX_SLEB64: 238 case R_WASM_MEMORY_ADDR_SLEB: 239 case R_WASM_MEMORY_ADDR_SLEB64: 240 return encodeSLEB128(static_cast<int64_t>(value), buf); 241 default: 242 llvm_unreachable("unexpected relocation type"); 243 } 244 } 245 246 static unsigned getRelocWidthPadded(const WasmRelocation &rel) { 247 switch (rel.Type) { 248 case R_WASM_TYPE_INDEX_LEB: 249 case R_WASM_FUNCTION_INDEX_LEB: 250 case R_WASM_GLOBAL_INDEX_LEB: 251 case R_WASM_EVENT_INDEX_LEB: 252 case R_WASM_MEMORY_ADDR_LEB: 253 case R_WASM_TABLE_INDEX_SLEB: 254 case R_WASM_MEMORY_ADDR_SLEB: 255 return 5; 256 case R_WASM_TABLE_INDEX_SLEB64: 257 case R_WASM_MEMORY_ADDR_LEB64: 258 case R_WASM_MEMORY_ADDR_SLEB64: 259 return 10; 260 default: 261 llvm_unreachable("unexpected relocation type"); 262 } 263 } 264 265 static unsigned getRelocWidth(const WasmRelocation &rel, uint64_t value) { 266 uint8_t buf[10]; 267 return writeCompressedReloc(buf, rel, value); 268 } 269 270 // Relocations of type LEB and SLEB in the code section are padded to 5 bytes 271 // so that a fast linker can blindly overwrite them without needing to worry 272 // about the number of bytes needed to encode the values. 273 // However, for optimal output the code section can be compressed to remove 274 // the padding then outputting non-relocatable files. 275 // In this case we need to perform a size calculation based on the value at each 276 // relocation. At best we end up saving 4 bytes for each relocation entry. 277 // 278 // This function only computes the final output size. It must be called 279 // before getSize() is used to calculate of layout of the code section. 280 void InputFunction::calculateSize() { 281 if (!file || !config->compressRelocations) 282 return; 283 284 LLVM_DEBUG(dbgs() << "calculateSize: " << getName() << "\n"); 285 286 const uint8_t *secStart = file->codeSection->Content.data(); 287 const uint8_t *funcStart = secStart + getInputSectionOffset(); 288 uint32_t functionSizeLength; 289 decodeULEB128(funcStart, &functionSizeLength); 290 291 uint32_t start = getInputSectionOffset(); 292 uint32_t end = start + function->Size; 293 294 uint32_t lastRelocEnd = start + functionSizeLength; 295 for (const WasmRelocation &rel : relocations) { 296 LLVM_DEBUG(dbgs() << " region: " << (rel.Offset - lastRelocEnd) << "\n"); 297 compressedFuncSize += rel.Offset - lastRelocEnd; 298 compressedFuncSize += getRelocWidth(rel, file->calcNewValue(rel)); 299 lastRelocEnd = rel.Offset + getRelocWidthPadded(rel); 300 } 301 LLVM_DEBUG(dbgs() << " final region: " << (end - lastRelocEnd) << "\n"); 302 compressedFuncSize += end - lastRelocEnd; 303 304 // Now we know how long the resulting function is we can add the encoding 305 // of its length 306 uint8_t buf[5]; 307 compressedSize = compressedFuncSize + encodeULEB128(compressedFuncSize, buf); 308 309 LLVM_DEBUG(dbgs() << " calculateSize orig: " << function->Size << "\n"); 310 LLVM_DEBUG(dbgs() << " calculateSize new: " << compressedSize << "\n"); 311 } 312 313 // Override the default writeTo method so that we can (optionally) write the 314 // compressed version of the function. 315 void InputFunction::writeTo(uint8_t *buf) const { 316 if (!file || !config->compressRelocations) 317 return InputChunk::writeTo(buf); 318 319 buf += outputOffset; 320 uint8_t *orig = buf; 321 (void)orig; 322 323 const uint8_t *secStart = file->codeSection->Content.data(); 324 const uint8_t *funcStart = secStart + getInputSectionOffset(); 325 const uint8_t *end = funcStart + function->Size; 326 uint32_t count; 327 decodeULEB128(funcStart, &count); 328 funcStart += count; 329 330 LLVM_DEBUG(dbgs() << "write func: " << getName() << "\n"); 331 buf += encodeULEB128(compressedFuncSize, buf); 332 const uint8_t *lastRelocEnd = funcStart; 333 for (const WasmRelocation &rel : relocations) { 334 unsigned chunkSize = (secStart + rel.Offset) - lastRelocEnd; 335 LLVM_DEBUG(dbgs() << " write chunk: " << chunkSize << "\n"); 336 memcpy(buf, lastRelocEnd, chunkSize); 337 buf += chunkSize; 338 buf += writeCompressedReloc(buf, rel, file->calcNewValue(rel)); 339 lastRelocEnd = secStart + rel.Offset + getRelocWidthPadded(rel); 340 } 341 342 unsigned chunkSize = end - lastRelocEnd; 343 LLVM_DEBUG(dbgs() << " write final chunk: " << chunkSize << "\n"); 344 memcpy(buf, lastRelocEnd, chunkSize); 345 LLVM_DEBUG(dbgs() << " total: " << (buf + chunkSize - orig) << "\n"); 346 } 347 348 // Generate code to apply relocations to the data section at runtime. 349 // This is only called when generating shared libaries (PIC) where address are 350 // not known at static link time. 351 void InputSegment::generateRelocationCode(raw_ostream &os) const { 352 LLVM_DEBUG(dbgs() << "generating runtime relocations: " << getName() 353 << " count=" << relocations.size() << "\n"); 354 355 unsigned opcode_ptr_const = config->is64.getValueOr(false) 356 ? WASM_OPCODE_I64_CONST 357 : WASM_OPCODE_I32_CONST; 358 unsigned opcode_ptr_add = config->is64.getValueOr(false) 359 ? WASM_OPCODE_I64_ADD 360 : WASM_OPCODE_I32_ADD; 361 362 // TODO(sbc): Encode the relocations in the data section and write a loop 363 // here to apply them. 364 uint64_t segmentVA = outputSeg->startVA + outputSegmentOffset; 365 for (const WasmRelocation &rel : relocations) { 366 uint64_t offset = rel.Offset - getInputSectionOffset(); 367 uint64_t outputOffset = segmentVA + offset; 368 369 LLVM_DEBUG(dbgs() << "gen reloc: type=" << relocTypeToString(rel.Type) 370 << " addend=" << rel.Addend << " index=" << rel.Index 371 << " output offset=" << outputOffset << "\n"); 372 373 // Get __memory_base 374 writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET"); 375 writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "memory_base"); 376 377 // Add the offset of the relocation 378 writeU8(os, opcode_ptr_const, "CONST"); 379 writeSleb128(os, outputOffset, "offset"); 380 writeU8(os, opcode_ptr_add, "ADD"); 381 382 bool is64 = relocIs64(rel.Type); 383 unsigned opcode_reloc_const = 384 is64 ? WASM_OPCODE_I64_CONST : WASM_OPCODE_I32_CONST; 385 unsigned opcode_reloc_add = 386 is64 ? WASM_OPCODE_I64_ADD : WASM_OPCODE_I32_ADD; 387 unsigned opcode_reloc_store = 388 is64 ? WASM_OPCODE_I64_STORE : WASM_OPCODE_I32_STORE; 389 390 Symbol *sym = file->getSymbol(rel); 391 // Now figure out what we want to store 392 if (sym->hasGOTIndex()) { 393 writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET"); 394 writeUleb128(os, sym->getGOTIndex(), "global index"); 395 if (rel.Addend) { 396 writeU8(os, opcode_reloc_const, "CONST"); 397 writeSleb128(os, rel.Addend, "addend"); 398 writeU8(os, opcode_reloc_add, "ADD"); 399 } 400 } else { 401 const GlobalSymbol* baseSymbol = WasmSym::memoryBase; 402 if (rel.Type == R_WASM_TABLE_INDEX_I32 || 403 rel.Type == R_WASM_TABLE_INDEX_I64) 404 baseSymbol = WasmSym::tableBase; 405 writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET"); 406 writeUleb128(os, baseSymbol->getGlobalIndex(), "base"); 407 writeU8(os, opcode_reloc_const, "CONST"); 408 writeSleb128(os, file->calcNewValue(rel), "offset"); 409 writeU8(os, opcode_reloc_add, "ADD"); 410 } 411 412 // Store that value at the virtual address 413 writeU8(os, opcode_reloc_store, "I32_STORE"); 414 writeUleb128(os, 2, "align"); 415 writeUleb128(os, 0, "offset"); 416 } 417 } 418 419 } // namespace wasm 420 } // namespace lld 421