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 }