lib/tldr/src/formats/elf/relocation/relax.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const root = @import("../../../root.zig");
  3 const elf = @import("../root.zig");
  4 const checked = @import("value.zig");
  5 
  6 const model = root.model;
  7 const Rela = elf.format.Rela;
  8 const copyInto = elf.format.copyInto;
  9 const writeU32 = elf.format.writeU32;
 10 
 11 fn checkedRelocationPatchOffset(
 12     contribution_size: u64,
 13     base_file_offset: u64,
 14     patch_offset: u64,
 15     patch_size: u64,
 16 ) model.Error!usize {
 17     if (patch_offset > contribution_size or
 18         patch_size > contribution_size - patch_offset) return error.InvalidRange;
 19     return @intCast(base_file_offset + patch_offset);
 20 }
 21 
 22 pub fn tlsGdToLocalExec(
 23     image: []u8,
 24     contribution_size: u64,
 25     base_file_offset: u64,
 26     relocation: Rela,
 27     value: i128,
 28 ) model.Error!void {
 29     if (relocation.offset < 4) return error.InvalidRange;
 30     const patch_offset = try checkedRelocationPatchOffset(contribution_size, base_file_offset, relocation.offset - 4, 16);
 31     const replacement = [_]u8{
 32         0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00, 0x00,
 33         0x00, 0x48, 0x8d, 0x80, 0x00, 0x00, 0x00, 0x00,
 34     };
 35     copyInto(image, patch_offset, &replacement);
 36     writeU32(image, patch_offset + 12, @bitCast(try checked.checkedI32(value)));
 37 }
 38 
 39 pub fn tlsLdToLocalExec(
 40     image: []u8,
 41     contribution_size: u64,
 42     base_file_offset: u64,
 43     relocation: Rela,
 44 ) model.Error!void {
 45     if (relocation.offset < 3) return error.InvalidRange;
 46     const probe_offset = try checkedRelocationPatchOffset(contribution_size, base_file_offset, relocation.offset - 3, 9);
 47     const direct_call_opcode_offset = probe_offset + 7;
 48     const replacement = [_]u8{
 49         0x66, 0x66, 0x66, 0x64, 0x48, 0x8b, 0x04, 0x25,
 50         0x00, 0x00, 0x00, 0x00,
 51     };
 52     if (image[direct_call_opcode_offset] == 0xe8) {
 53         const patch_offset = try checkedRelocationPatchOffset(contribution_size, base_file_offset, relocation.offset - 3, replacement.len);
 54         copyInto(image, patch_offset, &replacement);
 55         return;
 56     }
 57     if (image[direct_call_opcode_offset] == 0xff and image[direct_call_opcode_offset + 1] == 0x15) {
 58         const patch_offset = try checkedRelocationPatchOffset(contribution_size, base_file_offset, relocation.offset - 3, replacement.len + 1);
 59         image[patch_offset] = 0x66;
 60         copyInto(image, patch_offset + 1, &replacement);
 61         return;
 62     }
 63     return error.UnsupportedRelocation;
 64 }
 65 
 66 pub const Gotpcrelx = enum {
 67     pc_relative,
 68     absolute_signed_32,
 69 };
 70 
 71 pub fn gotpcrelxInstruction(image: []u8, place_offset: usize, relocation_type: u32) ?Gotpcrelx {
 72     if (relaxGotpcrelxMov(image, place_offset, relocation_type)) return .pc_relative;
 73     if (relaxGotpcrelxIndirectBranch(image, place_offset, relocation_type)) return .pc_relative;
 74     if (relaxGotpcrelxBinaryImmediate(image, place_offset, relocation_type)) return .absolute_signed_32;
 75     return null;
 76 }
 77 
 78 fn relaxGotpcrelxMov(image: []u8, place_offset: usize, relocation_type: u32) bool {
 79     if (place_offset < 2) return false;
 80     if (relocation_type == @backingInt(std.elf.R_X86_64.REX_GOTPCRELX)) {
 81         if (place_offset < 3 or !isRexPrefix(image[place_offset - 3])) return false;
 82     }
 83     const opcode_offset = place_offset - 2;
 84     if (image[opcode_offset] != 0x8b) return false;
 85     if (!isRipRelativeMemoryOperand(image[place_offset - 1])) return false;
 86     image[opcode_offset] = 0x8d;
 87     return true;
 88 }
 89 
 90 fn relaxGotpcrelxIndirectBranch(image: []u8, place_offset: usize, relocation_type: u32) bool {
 91     if (relocation_type == @backingInt(std.elf.R_X86_64.REX_GOTPCRELX)) return false;
 92     if (place_offset < 2) return false;
 93     const opcode_offset = place_offset - 2;
 94     if (image[opcode_offset] != 0xff) return false;
 95     const modrm = image[place_offset - 1];
 96     if (!isRipRelativeMemoryOperand(modrm)) return false;
 97     const reg = (modrm >> 3) & 0x7;
 98     const opcode: u8 = switch (reg) {
 99         2 => 0xe8,
100         4 => 0xe9,
101         else => return false,
102     };
103     image[opcode_offset] = 0x67;
104     image[opcode_offset + 1] = opcode;
105     return true;
106 }
107 
108 pub fn gotpcrelWeakUndefinedNullCheck(image: []u8, place_offset: usize) bool {
109     if (place_offset < 3 or place_offset + 4 >= image.len) return false;
110     const rex_offset = place_offset - 3;
111     const rex = image[rex_offset];
112     if (!isRexPrefix(rex) or (rex & 0x08) == 0) return false;
113     if (image[place_offset - 2] != 0x83) return false;
114     const modrm = image[place_offset - 1];
115     if (!isRipRelativeMemoryOperand(modrm) or ((modrm >> 3) & 0x7) != 7) return false;
116     if (image[place_offset + 4] != 0) return false;
117     const replacement = [_]u8{ 0x48, 0x39, 0xe4, 0x0f, 0x1f, 0x44, 0x00, 0x00 };
118     @memcpy(image[rex_offset..][0..replacement.len], &replacement);
119     return true;
120 }
121 
122 pub fn gottpoffToLocalExec(image: []u8, place_offset: usize, value: i128) model.Error!void {
123     if (place_offset < 3 or place_offset > image.len or 4 > image.len - place_offset) return error.InvalidRange;
124     const rex_offset = place_offset - 3;
125     const rex = image[rex_offset];
126     if (!isRexPrefix(rex) or (rex & 0x08) == 0) return error.UnsupportedRelocation;
127     if (image[place_offset - 2] != 0x8b) return error.UnsupportedRelocation;
128     const modrm = image[place_offset - 1];
129     if (!isRipRelativeMemoryOperand(modrm)) return error.UnsupportedRelocation;
130     const reg = (modrm >> 3) & 0x7;
131     const high_reg = (rex >> 2) & 0x1;
132     image[rex_offset] = 0x48 | high_reg;
133     image[place_offset - 2] = 0xc7;
134     image[place_offset - 1] = 0xc0 | reg;
135     writeU32(image, place_offset, @bitCast(try checked.checkedI32(value)));
136 }
137 
138 fn relaxGotpcrelxBinaryImmediate(image: []u8, place_offset: usize, relocation_type: u32) bool {
139     if (relocation_type != @backingInt(std.elf.R_X86_64.REX_GOTPCRELX)) return false;
140     if (place_offset < 3) return false;
141     const rex_offset = place_offset - 3;
142     const rex = image[rex_offset];
143     if (!isRexPrefix(rex) or (rex & 0x08) == 0) return false;
144     const opcode_offset = place_offset - 2;
145     const group_operation: u8 = switch (image[opcode_offset]) {
146         0x03 => 0,
147         0x0b => 1,
148         0x13 => 2,
149         0x1b => 3,
150         0x23 => 4,
151         0x2b => 5,
152         0x33 => 6,
153         0x3b => 7,
154         else => return false,
155     };
156     const modrm_offset = place_offset - 1;
157     const modrm = image[modrm_offset];
158     if (!isRipRelativeMemoryOperand(modrm)) return false;
159     const reg = (modrm >> 3) & 0x7;
160     const high_reg = (rex >> 2) & 0x1;
161     image[rex_offset] = 0x48 | high_reg;
162     image[opcode_offset] = 0x81;
163     image[modrm_offset] = 0xc0 | (group_operation << 3) | reg;
164     return true;
165 }
166 
167 fn isRexPrefix(byte: u8) bool {
168     return byte >= 0x40 and byte <= 0x4f;
169 }
170 
171 fn isRipRelativeMemoryOperand(modrm: u8) bool {
172     return (modrm & 0xc7) == 0x05;
173 }