v / vlib / v2 / gen / x64 / x64_pe_linker_test.v
2327 lines · 2094 sloc · 86.45 KB · ddb021b9866c3b4523b746fa2f4c16a594f8bd89
Raw
1module x64
2
3import os
4import rand
5import v2.mir
6
7fn pe_test_u16(data []u8, off int) u16 {
8 return u16(data[off]) | (u16(data[off + 1]) << 8)
9}
10
11fn pe_test_u32(data []u8, off int) u32 {
12 return u32(data[off]) | (u32(data[off + 1]) << 8) | (u32(data[off + 2]) << 16) | (u32(data[
13 off + 3]) << 24)
14}
15
16fn pe_test_u64(data []u8, off int) u64 {
17 return u64(pe_test_u32(data, off)) | (u64(pe_test_u32(data, off + 4)) << 32)
18}
19
20fn pe_test_i32(data []u8, off int) i32 {
21 return i32(pe_test_u32(data, off))
22}
23
24fn pe_test_rel8_target(instruction_off int, disp u8) int {
25 signed_disp := if int(disp) < 0x80 { int(disp) } else { int(disp) - 0x100 }
26 return instruction_off + 2 + signed_disp
27}
28
29fn pe_test_string(data []u8, off int) string {
30 mut end := off
31 for end < data.len && data[end] != 0 {
32 end++
33 }
34 return data[off..end].bytestr()
35}
36
37struct PeTestByteRange {
38 label string
39 start u32
40 limit u32
41}
42
43fn pe_test_byte_range(label string, start u32, size u32) PeTestByteRange {
44 return PeTestByteRange{
45 label: label
46 start: start
47 limit: start + size
48 }
49}
50
51fn pe_test_assert_range_in_file(image []u8, range PeTestByteRange) {
52 assert range.start <= range.limit
53 assert range.limit <= u32(image.len)
54}
55
56fn pe_test_assert_ranges_do_not_overlap(ranges []PeTestByteRange) {
57 for i in 0 .. ranges.len {
58 for j in i + 1 .. ranges.len {
59 if ranges[i].start == ranges[i].limit || ranges[j].start == ranges[j].limit {
60 continue
61 }
62 assert ranges[i].limit <= ranges[j].start || ranges[j].limit <= ranges[i].start
63 }
64 }
65}
66
67fn pe_test_section_header(image []u8, name string) int {
68 pe_off := int(pe_test_u32(image, 0x3c))
69 nsections := int(pe_test_u16(image, pe_off + 6))
70 section_off := pe_off + 4 + 20 + int(pe_test_u16(image, pe_off + 20))
71 for i in 0 .. nsections {
72 off := section_off + i * 40
73 if image[off..off + 8].bytestr().trim_right('\0') == name {
74 return off
75 }
76 }
77 return -1
78}
79
80fn pe_test_section_raw_ranges(image []u8) []PeTestByteRange {
81 pe_off := int(pe_test_u32(image, 0x3c))
82 nsections := int(pe_test_u16(image, pe_off + 6))
83 section_off := pe_off + 4 + 20 + int(pe_test_u16(image, pe_off + 20))
84 mut ranges := []PeTestByteRange{cap: nsections}
85 for i in 0 .. nsections {
86 off := section_off + i * 40
87 raw_size := pe_test_u32(image, off + 16)
88 raw_ptr := pe_test_u32(image, off + 20)
89 if raw_size == 0 {
90 continue
91 }
92 name := image[off..off + 8].bytestr().trim_right('\0')
93 ranges << pe_test_byte_range(name, raw_ptr, raw_size)
94 }
95 return ranges
96}
97
98fn pe_test_section_virtual_ranges(image []u8) []PeTestByteRange {
99 pe_off := int(pe_test_u32(image, 0x3c))
100 nsections := int(pe_test_u16(image, pe_off + 6))
101 section_off := pe_off + 4 + 20 + int(pe_test_u16(image, pe_off + 20))
102 mut ranges := []PeTestByteRange{cap: nsections}
103 for i in 0 .. nsections {
104 off := section_off + i * 40
105 virtual_size := pe_test_u32(image, off + 8)
106 virtual_address := pe_test_u32(image, off + 12)
107 name := image[off..off + 8].bytestr().trim_right('\0')
108 ranges << pe_test_byte_range(name, virtual_address, u32(align_int(int(virtual_size),
109 pe_section_alignment)))
110 }
111 return ranges
112}
113
114fn pe_test_assert_ranges_are_adjacent(ranges []PeTestByteRange) {
115 if ranges.len < 2 {
116 return
117 }
118 for i in 0 .. ranges.len - 1 {
119 assert ranges[i].limit == ranges[i + 1].start
120 }
121}
122
123fn pe_test_section_virtual_range(image []u8, name string) PeTestByteRange {
124 section_off := pe_test_section_header(image, name)
125 assert section_off >= 0
126 virtual_size := pe_test_u32(image, section_off + 8)
127 virtual_address := pe_test_u32(image, section_off + 12)
128 return pe_test_byte_range(name, virtual_address, u32(align_int(int(virtual_size),
129 pe_section_alignment)))
130}
131
132fn pe_test_assert_no_zero_sized_sections(image []u8) {
133 pe_off := int(pe_test_u32(image, 0x3c))
134 nsections := int(pe_test_u16(image, pe_off + 6))
135 section_off := pe_off + 4 + 20 + int(pe_test_u16(image, pe_off + 20))
136 for i in 0 .. nsections {
137 off := section_off + i * 40
138 virtual_size := pe_test_u32(image, off + 8)
139 raw_size := pe_test_u32(image, off + 16)
140 assert virtual_size != 0 || raw_size != 0
141 }
142}
143
144fn pe_test_assert_size_of_image_is_exact(image []u8) {
145 pe_off := int(pe_test_u32(image, 0x3c))
146 opt_off := pe_off + 4 + 20
147 ranges := pe_test_section_virtual_ranges(image)
148 assert ranges.len > 0
149 assert pe_test_u32(image, opt_off + 56) == ranges[ranges.len - 1].limit
150}
151
152fn pe_test_assert_entrypoint_in_executable_text(image []u8) {
153 pe_off := int(pe_test_u32(image, 0x3c))
154 opt_off := pe_off + 4 + 20
155 entry_rva := pe_test_u32(image, opt_off + 16)
156 text_off := pe_test_section_header(image, '.text')
157 text_range := pe_test_section_virtual_range(image, '.text')
158 assert entry_rva >= text_range.start
159 assert entry_rva < text_range.limit
160 assert pe_test_u32(image, text_off + 36) & pe_image_scn_mem_execute != 0
161}
162
163fn pe_test_assert_directory_contained_in_section(image []u8, directory_index int, section_name string) {
164 pe_off := int(pe_test_u32(image, 0x3c))
165 opt_off := pe_off + 4 + 20
166 dir_rva := pe_test_u32(image, opt_off + 112 + directory_index * 8)
167 dir_size := pe_test_u32(image, opt_off + 116 + directory_index * 8)
168 section_range := pe_test_section_virtual_range(image, section_name)
169 assert dir_rva >= section_range.start
170 assert dir_rva + dir_size <= section_range.limit
171}
172
173fn pe_test_rva_to_file_off(image []u8, rva u32) int {
174 pe_off := int(pe_test_u32(image, 0x3c))
175 nsections := int(pe_test_u16(image, pe_off + 6))
176 section_off := pe_off + 4 + 20 + int(pe_test_u16(image, pe_off + 20))
177 for i in 0 .. nsections {
178 off := section_off + i * 40
179 va := pe_test_u32(image, off + 12)
180 raw_size := pe_test_u32(image, off + 16)
181 raw_ptr := pe_test_u32(image, off + 20)
182 virtual_size := pe_test_u32(image, off + 8)
183 size := if virtual_size > raw_size { virtual_size } else { raw_size }
184 if rva >= va && rva < va + size {
185 return int(raw_ptr + (rva - va))
186 }
187 }
188 return -1
189}
190
191fn pe_test_import_descriptor_offsets(image []u8) []int {
192 pe_off := int(pe_test_u32(image, 0x3c))
193 opt_off := pe_off + 4 + 20
194 import_rva := pe_test_u32(image, opt_off + 112 + pe_import_directory_index * 8)
195 if import_rva == 0 {
196 return []
197 }
198 import_off := pe_test_rva_to_file_off(image, import_rva)
199 assert import_off > 0
200 mut offsets := []int{}
201 for i := 0; i < 32; i++ {
202 descriptor_off := import_off + i * 20
203 ilt_rva := pe_test_u32(image, descriptor_off)
204 dll_name_rva := pe_test_u32(image, descriptor_off + 12)
205 first_thunk_rva := pe_test_u32(image, descriptor_off + 16)
206 if ilt_rva == 0 && dll_name_rva == 0 && first_thunk_rva == 0 {
207 return offsets
208 }
209 assert ilt_rva != 0
210 assert dll_name_rva != 0
211 assert first_thunk_rva != 0
212 offsets << descriptor_off
213 }
214 assert false
215 return offsets
216}
217
218fn pe_test_import_names(image []u8) []string {
219 mut names := []string{}
220 for descriptor_off in pe_test_import_descriptor_offsets(image) {
221 ilt_rva := pe_test_u32(image, descriptor_off)
222 ilt_off := pe_test_rva_to_file_off(image, ilt_rva)
223 assert ilt_off > 0
224 for i := 0; i < 128; i++ {
225 hint_name_rva := u32(pe_test_u64(image, ilt_off + i * 8))
226 if hint_name_rva == 0 {
227 break
228 }
229 hint_name_off := pe_test_rva_to_file_off(image, hint_name_rva)
230 assert hint_name_off > 0
231 assert pe_test_u16(image, hint_name_off) == 0
232 assert hint_name_off % 2 == 0
233 names << pe_test_string(image, hint_name_off + 2)
234 }
235 }
236 return names
237}
238
239fn pe_test_import_dll_names(image []u8) []string {
240 mut dlls := []string{}
241 for descriptor_off in pe_test_import_descriptor_offsets(image) {
242 dll_name_rva := pe_test_u32(image, descriptor_off + 12)
243 dll_name_off := pe_test_rva_to_file_off(image, dll_name_rva)
244 assert dll_name_off > 0
245 dlls << pe_test_string(image, dll_name_off)
246 }
247 return dlls
248}
249
250fn pe_test_iat_size(image []u8) u32 {
251 pe_off := int(pe_test_u32(image, 0x3c))
252 opt_off := pe_off + 4 + 20
253 return pe_test_u32(image, opt_off + 116 + pe_iat_directory_index * 8)
254}
255
256fn pe_test_text_rel32_target(image []u8, text_rva u32, text_raw int, field_off int) u32 {
257 field_rva := text_rva + u32(field_off)
258 disp := pe_test_i32(image, text_raw + field_off)
259 return u32(i32(field_rva + 4) + disp)
260}
261
262fn pe_test_first_import_thunk_rva(image []u8, text_rva u32, text_raw int, text_start int) u32 {
263 text_off := pe_test_section_header(image, '.text')
264 text_raw_size := int(pe_test_u32(image, text_off + 16))
265 for off in text_raw + text_start .. text_raw + text_raw_size - 1 {
266 if image[off] == 0xff && image[off + 1] == 0x25 {
267 return text_rva + u32(off - text_raw)
268 }
269 }
270 assert false, 'missing PE import thunk in .text'
271 return 0
272}
273
274fn pe_test_import_thunk_rva(image []u8, text_rva u32, text_raw int, text_start int, name string) u32 {
275 names := pe_test_import_names(image)
276 idx := names.index(name)
277 if idx < 0 {
278 assert false, 'missing PE import `${name}` in ${names}'
279 return 0
280 }
281 return pe_test_first_import_thunk_rva(image, text_rva, text_raw, text_start) + u32(idx * 6)
282}
283
284fn sample_pe_coff_object() &CoffObject {
285 mut obj := CoffObject.new()
286 obj.text_data << [u8(0xc3), 0xc3]
287 obj.rodata << 'Hello World!'.bytes()
288 obj.rodata << 0
289 obj.data_data << [u8(1), 2, 3, 4]
290 obj.add_symbol('_vinit', 0, true, 1)
291 obj.add_symbol('main', 1, true, 1)
292 return obj
293}
294
295fn sample_pe_arguments_coff_object(with_vinit bool) &CoffObject {
296 mut obj := CoffObject.new()
297 mut main_off := 0
298 if with_vinit {
299 obj.text_data << u8(0xc3)
300 obj.add_symbol('_vinit', 0, true, 1)
301 main_off = obj.text_data.len
302 }
303 obj.text_data << [u8(0x8b), 0x05, 0, 0, 0, 0] // mov eax, [rip + g_main_argc]
304 obj.text_data << [u8(0x48), 0x8b, 0x15, 0, 0, 0, 0] // mov rdx, [rip + g_main_argv]
305 obj.text_data << u8(0xc3)
306 obj.add_symbol('main', u64(main_off), true, 1)
307 obj.data_data << [u8(0), 0, 0, 0, 0, 0, 0, 0]
308 obj.data_data << [u8(0), 0, 0, 0, 0, 0, 0, 0]
309 argc_sym := obj.add_symbol('g_main_argc', 0, false, 3)
310 argv_sym := obj.add_symbol('g_main_argv', 8, false, 3)
311 obj.add_text_reloc(main_off + 2, argc_sym, coff_image_rel_amd64_rel32)
312 obj.add_text_reloc(main_off + 9, argv_sym, coff_image_rel_amd64_rel32)
313 return obj
314}
315
316fn test_pe_linker_emits_pe32_plus_headers_and_sections() {
317 obj := sample_pe_coff_object()
318 mut linker := PeLinker.new(obj)
319 image := linker.image() or { panic(err) }
320
321 assert image[0] == `M`
322 assert image[1] == `Z`
323 pe_off := int(pe_test_u32(image, 0x3c))
324 assert pe_off == pe_dos_stub_size
325 assert pe_test_u32(image, pe_off) == pe_signature
326 assert pe_test_u16(image, pe_off + 4) == coff_image_file_machine_amd64
327 assert pe_test_u16(image, pe_off + 6) == 4
328 assert pe_test_u32(image, pe_off + 8) == 0
329 assert pe_test_u32(image, pe_off + 12) == 0
330 assert pe_test_u32(image, pe_off + 16) == 0
331 assert pe_test_u16(image, pe_off + 20) == pe_size_of_optional_header64
332 assert pe_test_u16(image, pe_off + 22) & pe_image_file_relocs_stripped != 0
333 assert pe_test_u16(image, pe_off + 22) & pe_image_file_executable_image != 0
334 assert pe_test_u16(image, pe_off + 22) & pe_image_file_large_address_aware != 0
335
336 opt_off := pe_off + 4 + 20
337 assert pe_test_u16(image, opt_off) == pe_optional_header64_magic
338 assert image[opt_off + 2] == pe_linker_major_version
339 assert image[opt_off + 3] == pe_linker_minor_version
340 assert pe_test_u32(image, opt_off + 4) != 0
341 assert pe_test_u32(image, opt_off + 8) != 0
342 assert pe_test_u32(image, opt_off + 12) == 0
343 assert pe_test_u32(image, opt_off + 16) == pe_section_alignment
344 assert pe_test_u32(image, opt_off + 20) == pe_section_alignment
345 assert pe_test_u64(image, opt_off + 24) == pe_image_base
346 assert pe_test_u32(image, opt_off + 32) == pe_section_alignment
347 assert pe_test_u32(image, opt_off + 36) == pe_file_alignment
348 assert pe_test_u16(image, opt_off + 40) == pe_major_operating_system_version
349 assert pe_test_u16(image, opt_off + 42) == pe_minor_operating_system_version
350 assert pe_test_u16(image, opt_off + 48) == pe_major_subsystem_version
351 assert pe_test_u16(image, opt_off + 50) == pe_minor_subsystem_version
352 assert pe_test_u32(image, opt_off + 52) == 0
353 assert pe_test_u32(image, opt_off + 60) == pe_headers_size(4)
354 assert pe_test_u32(image, opt_off + 64) == 0
355 assert pe_test_u16(image, opt_off + 68) == pe_image_subsystem_windows_cui
356 assert pe_test_u16(image, opt_off + 70) & pe_dll_characteristics_nx_compat != 0
357 assert pe_test_u64(image, opt_off + 72) == pe_size_of_stack_reserve
358 assert pe_test_u64(image, opt_off + 80) == pe_size_of_stack_commit
359 assert pe_test_u64(image, opt_off + 88) == pe_size_of_heap_reserve
360 assert pe_test_u64(image, opt_off + 96) == pe_size_of_heap_commit
361 assert pe_test_u32(image, opt_off + 104) == 0
362 assert pe_test_u32(image, opt_off + 108) == pe_number_of_rva_and_sizes
363
364 text_off := pe_test_section_header(image, '.text')
365 rdata_off := pe_test_section_header(image, '.rdata')
366 data_off := pe_test_section_header(image, '.data')
367 idata_off := pe_test_section_header(image, '.idata')
368 assert text_off > 0
369 assert rdata_off > text_off
370 assert data_off > rdata_off
371 assert idata_off > data_off
372 mut file_ranges := [
373 pe_test_byte_range('PE headers', 0, pe_test_u32(image, opt_off + 60)),
374 ]
375 file_ranges << pe_test_section_raw_ranges(image)
376 for range in file_ranges {
377 pe_test_assert_range_in_file(image, range)
378 }
379 pe_test_assert_ranges_do_not_overlap(file_ranges)
380 pe_test_assert_ranges_are_adjacent(pe_test_section_virtual_ranges(image))
381 pe_test_assert_no_zero_sized_sections(image)
382 pe_test_assert_size_of_image_is_exact(image)
383 pe_test_assert_entrypoint_in_executable_text(image)
384 pe_test_assert_directory_contained_in_section(image, pe_import_directory_index, '.idata')
385 pe_test_assert_directory_contained_in_section(image, pe_iat_directory_index, '.idata')
386 assert pe_test_u32(image, text_off + 8) != 0
387 assert pe_test_u32(image, text_off + 12) == pe_section_alignment
388 assert pe_test_u32(image, text_off + 20) == pe_headers_size(4)
389 assert pe_test_u32(image, text_off + 20) % pe_file_alignment == 0
390 assert pe_test_u32(image, text_off + 16) % pe_file_alignment == 0
391 assert pe_test_u32(image, text_off + 24) == 0
392 assert pe_test_u32(image, text_off + 28) == 0
393 assert pe_test_u16(image, text_off + 32) == 0
394 assert pe_test_u16(image, text_off + 34) == 0
395 assert pe_test_u32(image, text_off + 36) & pe_image_scn_cnt_code != 0
396 assert pe_test_u32(image, text_off + 36) & pe_image_scn_mem_execute != 0
397 assert pe_test_u32(image, text_off + 36) & pe_image_scn_mem_read != 0
398 assert pe_test_u32(image, rdata_off + 8) == u32(obj.rodata.len)
399 assert pe_test_u32(image, rdata_off + 12) % pe_section_alignment == 0
400 assert pe_test_u32(image, rdata_off + 16) % pe_file_alignment == 0
401 assert pe_test_u32(image, rdata_off + 20) % pe_file_alignment == 0
402 assert pe_test_u32(image, rdata_off + 24) == 0
403 assert pe_test_u16(image, rdata_off + 32) == 0
404 assert pe_test_u32(image, rdata_off + 36) & pe_image_scn_cnt_initialized_data != 0
405 assert pe_test_u32(image, rdata_off + 36) & pe_image_scn_mem_read != 0
406 assert pe_test_u32(image, data_off + 8) == u32(obj.data_data.len)
407 assert pe_test_u32(image, data_off + 16) % pe_file_alignment == 0
408 assert pe_test_u32(image, data_off + 20) % pe_file_alignment == 0
409 assert pe_test_u32(image, data_off + 24) == 0
410 assert pe_test_u16(image, data_off + 32) == 0
411 assert pe_test_u32(image, data_off + 36) & pe_image_scn_cnt_initialized_data != 0
412 assert pe_test_u32(image, data_off + 36) & pe_image_scn_mem_read != 0
413 assert pe_test_u32(image, data_off + 36) & pe_image_scn_mem_write != 0
414 assert pe_test_u32(image, idata_off + 8) != 0
415 assert pe_test_u32(image, idata_off + 12) % pe_section_alignment == 0
416 assert pe_test_u32(image, idata_off + 16) % pe_file_alignment == 0
417 assert pe_test_u32(image, idata_off + 20) % pe_file_alignment == 0
418 assert pe_test_u32(image, idata_off + 24) == 0
419 assert pe_test_u16(image, idata_off + 32) == 0
420 assert pe_test_u32(image, idata_off + 36) & pe_image_scn_cnt_initialized_data != 0
421 assert pe_test_u32(image, idata_off + 36) & pe_image_scn_mem_read != 0
422 assert pe_test_u32(image, idata_off + 36) & pe_image_scn_mem_write != 0
423
424 idata_end := pe_test_u32(image, idata_off + 12) + pe_section_alignment
425 assert pe_test_u32(image, opt_off + 56) == idata_end
426}
427
428fn test_pe_linker_emits_demand_driven_kernel32_import_table() {
429 obj := sample_pe_coff_object()
430 mut linker := PeLinker.new(obj)
431 image := linker.image() or { panic(err) }
432 expected_imports := ['ExitProcess']
433
434 pe_off := int(pe_test_u32(image, 0x3c))
435 opt_off := pe_off + 4 + 20
436 import_rva := pe_test_u32(image, opt_off + 112 + pe_import_directory_index * 8)
437 import_size := pe_test_u32(image, opt_off + 116 + pe_import_directory_index * 8)
438 iat_rva := pe_test_u32(image, opt_off + 112 + pe_iat_directory_index * 8)
439 iat_size := pe_test_u32(image, opt_off + 116 + pe_iat_directory_index * 8)
440 assert import_rva != 0
441 assert import_size == 40
442 assert iat_rva != 0
443 assert iat_size == u32((expected_imports.len + 1) * 8)
444
445 import_off := pe_test_rva_to_file_off(image, import_rva)
446 assert import_off > 0
447 ilt_rva := pe_test_u32(image, import_off)
448 dll_name_rva := pe_test_u32(image, import_off + 12)
449 first_thunk_rva := pe_test_u32(image, import_off + 16)
450 assert ilt_rva != 0
451 assert first_thunk_rva == iat_rva
452 assert pe_test_string(image, pe_test_rva_to_file_off(image, dll_name_rva)) == 'kernel32.dll'
453 for i in 0 .. 20 {
454 assert image[import_off + 20 + i] == 0
455 }
456
457 mut names := []string{}
458 ilt_off := pe_test_rva_to_file_off(image, ilt_rva)
459 iat_off := pe_test_rva_to_file_off(image, iat_rva)
460 for i in 0 .. expected_imports.len {
461 hint_name_rva := u32(pe_test_u64(image, ilt_off + i * 8))
462 hint_name_off := pe_test_rva_to_file_off(image, hint_name_rva)
463 assert pe_test_u16(image, hint_name_off) == 0
464 assert hint_name_off % 2 == 0
465 names << pe_test_string(image, hint_name_off + 2)
466 assert pe_test_u64(image, iat_off + i * 8) == pe_test_u64(image, ilt_off + i * 8)
467 }
468 assert pe_test_u64(image, ilt_off + expected_imports.len * 8) == 0
469 assert pe_test_u64(image, iat_off + expected_imports.len * 8) == 0
470 assert names == expected_imports
471 assert names == pe_test_import_names(image)
472 assert pe_test_import_dll_names(image) == ['kernel32.dll']
473 assert pe_test_iat_size(image) == u32((expected_imports.len + 1) * 8)
474 assert 'HeapAlloc' !in names
475 assert 'WriteFile' !in names
476 assert 'GetCommandLineW' !in names
477 assert 'CommandLineToArgvW' !in names
478}
479
480fn test_pe_linker_adds_direct_kernel32_imports_in_stable_order() {
481 mut obj := CoffObject.new()
482 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3]
483 obj.add_symbol('main', 10, true, 1)
484 write_file_sym := obj.add_undefined('WriteFile')
485 get_std_handle_sym := obj.add_undefined('GetStdHandle')
486 obj.add_text_reloc(1, write_file_sym, coff_image_rel_amd64_rel32)
487 obj.add_text_reloc(6, get_std_handle_sym, coff_image_rel_amd64_rel32)
488
489 mut linker := PeLinker.new(obj)
490 image := linker.image() or { panic(err) }
491 names := pe_test_import_names(image)
492 dll_names := pe_test_import_dll_names(image)
493
494 assert names == ['ExitProcess', 'GetStdHandle', 'WriteFile']
495 assert dll_names == ['kernel32.dll']
496 assert pe_test_iat_size(image) == u32((names.len + 1) * 8)
497 assert 'ucrtbase.dll' !in dll_names
498}
499
500fn test_pe_linker_imports_kernel32_readconsole_readfile_and_patches_call_to_import_thunks() {
501 mut obj := CoffObject.new()
502 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3]
503 obj.add_symbol('main', 10, true, 1)
504 read_console_sym := obj.add_undefined('ReadConsole')
505 read_file_sym := obj.add_undefined('ReadFile')
506 obj.add_text_reloc(1, read_console_sym, coff_image_rel_amd64_rel32)
507 obj.add_text_reloc(6, read_file_sym, coff_image_rel_amd64_rel32)
508
509 mut linker := PeLinker.new(obj)
510 image := linker.image() or { panic(err) }
511 names := pe_test_import_names(image)
512 dll_names := pe_test_import_dll_names(image)
513
514 assert names == ['ExitProcess', 'ReadConsoleW', 'ReadFile']
515 assert dll_names == ['kernel32.dll']
516 assert pe_test_iat_size(image) == u32((names.len + 1) * 8)
517
518 text_off := pe_test_section_header(image, '.text')
519 text_rva := pe_test_u32(image, text_off + 12)
520 text_raw := int(pe_test_u32(image, text_off + 20))
521 read_console_thunk := pe_test_import_thunk_rva(image, text_rva, text_raw,
522
523 linker.entry_stub_size() + obj.text_data.len, 'ReadConsoleW')
524 read_file_thunk := pe_test_import_thunk_rva(image, text_rva, text_raw,
525
526 linker.entry_stub_size() + obj.text_data.len, 'ReadFile')
527 read_console_field_off := text_raw + linker.entry_stub_size() + 1
528 read_console_field_rva := text_rva + u32(linker.entry_stub_size() + 1)
529 read_console_disp := pe_test_i32(image, read_console_field_off)
530 read_console_target := u32(i32(read_console_field_rva + 4) + read_console_disp)
531 assert read_console_target == read_console_thunk
532 read_file_field_off := text_raw + linker.entry_stub_size() + 6
533 read_file_field_rva := text_rva + u32(linker.entry_stub_size() + 6)
534 read_file_disp := pe_test_i32(image, read_file_field_off)
535 read_file_target := u32(i32(read_file_field_rva + 4) + read_file_disp)
536 assert read_file_target == read_file_thunk
537}
538
539fn test_pe_linker_imports_ucrt_log_and_patches_call_to_import_thunk() {
540 mut obj := CoffObject.new()
541 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
542 obj.add_symbol('main', 5, true, 1)
543 log_sym := obj.add_undefined('log')
544 obj.add_text_reloc(1, log_sym, coff_image_rel_amd64_rel32)
545
546 mut linker := PeLinker.new(obj)
547 image := linker.image() or { panic(err) }
548 names := pe_test_import_names(image)
549 dll_names := pe_test_import_dll_names(image)
550
551 assert names == ['ExitProcess', 'log']
552 assert dll_names == ['kernel32.dll', 'ucrtbase.dll']
553 assert pe_test_iat_size(image) == u32((names.len + dll_names.len) * 8)
554
555 text_off := pe_test_section_header(image, '.text')
556 text_rva := pe_test_u32(image, text_off + 12)
557 text_raw := int(pe_test_u32(image, text_off + 20))
558 field_off := text_raw + linker.entry_stub_size() + 1
559 field_rva := text_rva + u32(linker.entry_stub_size() + 1)
560 disp := pe_test_i32(image, field_off)
561 target := u32(i32(field_rva + 4) + disp)
562 log_thunk := pe_test_import_thunk_rva(image, text_rva, text_raw, linker.entry_stub_size() +
563 obj.text_data.len, 'log')
564 assert target == log_thunk
565}
566
567fn test_pe_linker_imports_ucrt_ldexp_and_patches_call_to_import_thunk() {
568 mut obj := CoffObject.new()
569 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
570 obj.add_symbol('main', 5, true, 1)
571 ldexp_sym := obj.add_undefined('ldexp')
572 obj.add_text_reloc(1, ldexp_sym, coff_image_rel_amd64_rel32)
573
574 mut linker := PeLinker.new(obj)
575 image := linker.image() or { panic(err) }
576 names := pe_test_import_names(image)
577 dll_names := pe_test_import_dll_names(image)
578
579 assert names == ['ExitProcess', 'ldexp']
580 assert dll_names == ['kernel32.dll', 'ucrtbase.dll']
581 assert pe_test_iat_size(image) == u32((names.len + dll_names.len) * 8)
582
583 text_off := pe_test_section_header(image, '.text')
584 text_rva := pe_test_u32(image, text_off + 12)
585 text_raw := int(pe_test_u32(image, text_off + 20))
586 field_off := text_raw + linker.entry_stub_size() + 1
587 field_rva := text_rva + u32(linker.entry_stub_size() + 1)
588 disp := pe_test_i32(image, field_off)
589 target := u32(i32(field_rva + 4) + disp)
590 ldexp_thunk := pe_test_import_thunk_rva(image, text_rva, text_raw, linker.entry_stub_size() +
591 obj.text_data.len, 'ldexp')
592 assert target == ldexp_thunk
593}
594
595fn test_pe_linker_imports_ucrt_sqrt_and_patches_call_to_import_thunk() {
596 mut obj := CoffObject.new()
597 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
598 obj.add_symbol('main', 5, true, 1)
599 sqrt_sym := obj.add_undefined('sqrt')
600 obj.add_text_reloc(1, sqrt_sym, coff_image_rel_amd64_rel32)
601
602 mut linker := PeLinker.new(obj)
603 image := linker.image() or { panic(err) }
604 names := pe_test_import_names(image)
605 dll_names := pe_test_import_dll_names(image)
606
607 assert names == ['ExitProcess', 'sqrt']
608 assert dll_names == ['kernel32.dll', 'ucrtbase.dll']
609 assert pe_test_iat_size(image) == u32((names.len + dll_names.len) * 8)
610
611 text_off := pe_test_section_header(image, '.text')
612 text_rva := pe_test_u32(image, text_off + 12)
613 text_raw := int(pe_test_u32(image, text_off + 20))
614 field_off := text_raw + linker.entry_stub_size() + 1
615 field_rva := text_rva + u32(linker.entry_stub_size() + 1)
616 disp := pe_test_i32(image, field_off)
617 target := u32(i32(field_rva + 4) + disp)
618 sqrt_thunk := pe_test_import_thunk_rva(image, text_rva, text_raw, linker.entry_stub_size() +
619 obj.text_data.len, 'sqrt')
620 assert target == sqrt_thunk
621}
622
623fn test_pe_linker_maps_time_and_localtime_to_ucrt64_imports_and_patches_thunks() {
624 mut obj := CoffObject.new()
625 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3]
626 obj.add_symbol('main', 10, true, 1)
627 time_sym := obj.add_undefined('time')
628 localtime_sym := obj.add_undefined('localtime')
629 obj.add_text_reloc(1, time_sym, coff_image_rel_amd64_rel32)
630 obj.add_text_reloc(6, localtime_sym, coff_image_rel_amd64_rel32)
631
632 mut linker := PeLinker.new(obj)
633 image := linker.image() or { panic(err) }
634 names := pe_test_import_names(image)
635 dll_names := pe_test_import_dll_names(image)
636
637 assert names == ['ExitProcess', '_time64', '_localtime64']
638 assert dll_names == ['kernel32.dll', 'ucrtbase.dll']
639 assert 'time' !in names
640 assert 'localtime' !in names
641 assert pe_test_iat_size(image) == u32((names.len + dll_names.len) * 8)
642
643 text_off := pe_test_section_header(image, '.text')
644 text_rva := pe_test_u32(image, text_off + 12)
645 text_raw := int(pe_test_u32(image, text_off + 20))
646 time_thunk := pe_test_import_thunk_rva(image, text_rva, text_raw, linker.entry_stub_size() +
647 obj.text_data.len, '_time64')
648 localtime_thunk := pe_test_import_thunk_rva(image, text_rva, text_raw,
649
650 linker.entry_stub_size() + obj.text_data.len, '_localtime64')
651 time_field_off := text_raw + linker.entry_stub_size() + 1
652 time_field_rva := text_rva + u32(linker.entry_stub_size() + 1)
653 time_disp := pe_test_i32(image, time_field_off)
654 time_target := u32(i32(time_field_rva + 4) + time_disp)
655 assert time_target == time_thunk
656 localtime_field_off := text_raw + linker.entry_stub_size() + 6
657 localtime_field_rva := text_rva + u32(linker.entry_stub_size() + 6)
658 localtime_disp := pe_test_i32(image, localtime_field_off)
659 localtime_target := u32(i32(localtime_field_rva + 4) + localtime_disp)
660 assert localtime_target == localtime_thunk
661}
662
663fn test_pe_linker_imports_msvcrt_scprintf_snprintf_and_patches_call_to_import_thunks() {
664 mut obj := CoffObject.new()
665 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3]
666 obj.add_symbol('main', 10, true, 1)
667 scprintf_sym := obj.add_undefined('_scprintf')
668 snprintf_sym := obj.add_undefined('_snprintf')
669 obj.add_text_reloc(1, scprintf_sym, coff_image_rel_amd64_rel32)
670 obj.add_text_reloc(6, snprintf_sym, coff_image_rel_amd64_rel32)
671
672 mut linker := PeLinker.new(obj)
673 image := linker.image() or { panic(err) }
674 names := pe_test_import_names(image)
675 dll_names := pe_test_import_dll_names(image)
676
677 assert names == ['ExitProcess', '_scprintf', '_snprintf']
678 assert dll_names == ['kernel32.dll', 'msvcrt.dll']
679 assert pe_test_iat_size(image) == u32((names.len + dll_names.len) * 8)
680
681 text_off := pe_test_section_header(image, '.text')
682 text_rva := pe_test_u32(image, text_off + 12)
683 text_raw := int(pe_test_u32(image, text_off + 20))
684 scprintf_thunk := pe_test_import_thunk_rva(image, text_rva, text_raw,
685
686 linker.entry_stub_size() + obj.text_data.len, '_scprintf')
687 snprintf_thunk := pe_test_import_thunk_rva(image, text_rva, text_raw,
688
689 linker.entry_stub_size() + obj.text_data.len, '_snprintf')
690 scprintf_field_off := text_raw + linker.entry_stub_size() + 1
691 scprintf_field_rva := text_rva + u32(linker.entry_stub_size() + 1)
692 scprintf_disp := pe_test_i32(image, scprintf_field_off)
693 scprintf_target := u32(i32(scprintf_field_rva + 4) + scprintf_disp)
694 assert scprintf_target == scprintf_thunk
695 snprintf_field_off := text_raw + linker.entry_stub_size() + 6
696 snprintf_field_rva := text_rva + u32(linker.entry_stub_size() + 6)
697 snprintf_disp := pe_test_i32(image, snprintf_field_off)
698 snprintf_target := u32(i32(snprintf_field_rva + 4) + snprintf_disp)
699 assert snprintf_target == snprintf_thunk
700}
701
702fn test_pe_linker_resolves_atexit_with_internal_runtime_not_crt_import() {
703 mut obj := CoffObject.new()
704 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
705 obj.add_symbol('main', 5, true, 1)
706 atexit_sym := obj.add_undefined('atexit')
707 obj.add_text_reloc(1, atexit_sym, coff_image_rel_amd64_rel32)
708
709 mut linker := PeLinker.new(obj)
710 image := linker.image() or { panic(err) }
711 names := pe_test_import_names(image)
712 dll_names := pe_test_import_dll_names(image)
713
714 assert names == ['ExitProcess']
715 assert dll_names == ['kernel32.dll']
716 assert 'atexit' !in names
717 assert 'ucrtbase.dll' !in dll_names
718 assert 'msvcrt.dll' !in dll_names
719 assert pe_test_section_header(image, '.data') >= 0
720
721 text_off := pe_test_section_header(image, '.text')
722 text_rva := pe_test_u32(image, text_off + 12)
723 text_raw := int(pe_test_u32(image, text_off + 20))
724 entry_stub_len := linker.entry_stub_size()
725 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
726 first_import_thunk_rva := pe_test_first_import_thunk_rva(image, text_rva, text_raw,
727
728 entry_stub_len + obj.text_data.len)
729 field_off := entry_stub_len + 1
730 target := pe_test_text_rel32_target(image, text_rva, text_raw, field_off)
731
732 assert target >= runtime_start_rva
733 assert target < first_import_thunk_rva
734}
735
736fn test_pe_linker_adds_runtime_kernel32_import_patches_only_when_needed() {
737 mut obj := CoffObject.new()
738 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
739 obj.add_symbol('main', 5, true, 1)
740 calloc_sym := obj.add_undefined('calloc')
741 obj.add_text_reloc(1, calloc_sym, coff_image_rel_amd64_rel32)
742
743 mut linker := PeLinker.new(obj)
744 image := linker.image() or { panic(err) }
745 names := pe_test_import_names(image)
746
747 assert names == ['ExitProcess', 'GetProcessHeap', 'HeapAlloc']
748 assert pe_test_iat_size(image) == u32((names.len + 1) * 8)
749 assert 'HeapFree' !in names
750 assert 'WriteFile' !in names
751}
752
753fn test_pe_linker_adds_malloc_heap_imports_without_crt() {
754 mut obj := CoffObject.new()
755 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
756 obj.add_symbol('main', 5, true, 1)
757 malloc_sym := obj.add_undefined('malloc')
758 obj.add_text_reloc(1, malloc_sym, coff_image_rel_amd64_rel32)
759
760 mut linker := PeLinker.new(obj)
761 image := linker.image() or { panic(err) }
762 names := pe_test_import_names(image)
763 dll_names := pe_test_import_dll_names(image)
764
765 assert names == ['ExitProcess', 'GetProcessHeap', 'HeapAlloc']
766 assert dll_names == ['kernel32.dll']
767 assert pe_test_iat_size(image) == u32((names.len + 1) * 8)
768 assert 'HeapFree' !in names
769 assert 'HeapReAlloc' !in names
770 assert 'ucrtbase.dll' !in dll_names
771 assert 'msvcrt.dll' !in dll_names
772}
773
774fn test_pe_linker_adds_shell32_imports_only_for_windows_arguments_globals() {
775 obj := sample_pe_arguments_coff_object(false)
776 mut linker := PeLinker.new(obj)
777 image := linker.image() or { panic(err) }
778 names := pe_test_import_names(image)
779
780 assert names == ['ExitProcess', 'GetCommandLineW', 'CommandLineToArgvW']
781 assert pe_test_import_dll_names(image) == ['kernel32.dll', 'shell32.dll']
782 assert pe_test_iat_size(image) == u32((names.len + 2) * 8)
783}
784
785fn test_pe_linker_uses_qualified_import_keys_for_multi_dll_thunks_and_iat() {
786 obj := sample_pe_coff_object()
787 mut linker := PeLinker.new(obj)
788 linker.imports = [
789 PeImport{
790 dll: pe_kernel32_dll
791 name: 'SharedExport'
792 },
793 PeImport{
794 dll: pe_shell32_dll
795 name: 'SharedExport'
796 },
797 ]
798
799 kernel_key := pe_import_key(pe_kernel32_dll, 'SharedExport')
800 shell_key := pe_import_key(pe_shell32_dll, 'SharedExport')
801 import_offsets := linker.import_thunk_offsets(0)
802 idata := linker.build_idata(0x3000)
803
804 assert 'SharedExport' !in import_offsets
805 assert kernel_key in import_offsets
806 assert shell_key in import_offsets
807 assert import_offsets[kernel_key] != import_offsets[shell_key]
808 assert 'SharedExport' !in idata.iat_rvas
809 assert kernel_key in idata.iat_rvas
810 assert shell_key in idata.iat_rvas
811 assert idata.iat_rvas[kernel_key] != idata.iat_rvas[shell_key]
812}
813
814fn test_pe_linker_resolves_windows_string_plus_runtime_with_heap_imports() {
815 mut obj := CoffObject.new()
816 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
817 obj.add_symbol('main', 5, true, 1)
818 string_plus_sym := obj.add_undefined('builtin__string__+')
819 obj.add_text_reloc(1, string_plus_sym, coff_image_rel_amd64_rel32)
820
821 mut linker := PeLinker.new(obj)
822 image := linker.image() or { panic(err) }
823 names := pe_test_import_names(image)
824
825 assert names == ['ExitProcess', 'GetProcessHeap', 'HeapAlloc']
826 assert pe_test_iat_size(image) == u32((names.len + 1) * 8)
827 assert 'WriteFile' !in names
828}
829
830fn test_pe_linker_resolves_windows_i64_str_runtime_with_heap_imports() {
831 mut obj := CoffObject.new()
832 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
833 obj.add_symbol('main', 5, true, 1)
834 i64_str_sym := obj.add_undefined('builtin__i64__str')
835 obj.add_text_reloc(1, i64_str_sym, coff_image_rel_amd64_rel32)
836
837 mut linker := PeLinker.new(obj)
838 image := linker.image() or { panic(err) }
839 names := pe_test_import_names(image)
840
841 assert names == ['ExitProcess', 'GetProcessHeap', 'HeapAlloc']
842 assert pe_test_iat_size(image) == u32((names.len + 1) * 8)
843 assert 'WriteFile' !in names
844}
845
846fn test_pe_linker_zeroes_unemitted_data_directories() {
847 obj := sample_pe_coff_object()
848 mut linker := PeLinker.new(obj)
849 image := linker.image() or { panic(err) }
850
851 pe_off := int(pe_test_u32(image, 0x3c))
852 opt_off := pe_off + 4 + 20
853 for i in 0 .. pe_number_of_rva_and_sizes {
854 if i in [pe_import_directory_index, pe_iat_directory_index] {
855 continue
856 }
857 assert pe_test_u32(image, opt_off + 112 + i * 8) == 0
858 assert pe_test_u32(image, opt_off + 116 + i * 8) == 0
859 }
860 assert pe_test_u32(image, opt_off + 112 + pe_exception_directory_index * 8) == 0
861 assert pe_test_u32(image, opt_off + 116 + pe_exception_directory_index * 8) == 0
862 assert pe_test_u32(image, opt_off + 112 + pe_base_reloc_directory_index * 8) == 0
863 assert pe_test_u32(image, opt_off + 116 + pe_base_reloc_directory_index * 8) == 0
864}
865
866fn test_pe_linker_marks_fixed_base_image_without_unwind_or_reloc_directories() {
867 obj := sample_pe_coff_object()
868 mut linker := PeLinker.new(obj)
869 image := linker.image() or { panic(err) }
870
871 pe_off := int(pe_test_u32(image, 0x3c))
872 characteristics := pe_test_u16(image, pe_off + 22)
873 assert characteristics & pe_image_file_relocs_stripped != 0
874
875 opt_off := pe_off + 4 + 20
876 assert pe_test_u32(image, opt_off + 112 + pe_base_reloc_directory_index * 8) == 0
877 assert pe_test_u32(image, opt_off + 116 + pe_base_reloc_directory_index * 8) == 0
878 assert pe_test_u32(image, opt_off + 112 + pe_exception_directory_index * 8) == 0
879 assert pe_test_u32(image, opt_off + 116 + pe_exception_directory_index * 8) == 0
880}
881
882fn test_pe_linker_omits_empty_rdata_and_data_sections() {
883 mut obj := CoffObject.new()
884 obj.text_data << u8(0xc3)
885 obj.add_symbol('main', 0, true, 1)
886
887 mut linker := PeLinker.new(obj)
888 image := linker.image() or { panic(err) }
889 pe_off := int(pe_test_u32(image, 0x3c))
890 opt_off := pe_off + 4 + 20
891 text_off := pe_test_section_header(image, '.text')
892 idata_off := pe_test_section_header(image, '.idata')
893
894 assert pe_test_u16(image, pe_off + 6) == 2
895 assert text_off > 0
896 assert pe_test_section_header(image, '.rdata') == -1
897 assert pe_test_section_header(image, '.data') == -1
898 assert idata_off > text_off
899
900 assert pe_test_u32(image, text_off + 12) == pe_section_alignment
901 assert pe_test_u32(image, idata_off + 12) == pe_section_alignment * 2
902 assert pe_test_u32(image, idata_off + 20) == pe_headers_size(2) + pe_file_alignment
903 assert pe_test_u32(image, opt_off + 56) == pe_section_alignment * 3
904 pe_test_assert_ranges_are_adjacent(pe_test_section_virtual_ranges(image))
905 pe_test_assert_no_zero_sized_sections(image)
906 pe_test_assert_size_of_image_is_exact(image)
907 pe_test_assert_entrypoint_in_executable_text(image)
908 pe_test_assert_directory_contained_in_section(image, pe_import_directory_index, '.idata')
909 pe_test_assert_directory_contained_in_section(image, pe_iat_directory_index, '.idata')
910}
911
912fn test_pe_linker_entry_stub_targets_main_and_runtime_exit() {
913 obj := sample_pe_coff_object()
914 mut linker := PeLinker.new(obj)
915 image := linker.image() or { panic(err) }
916
917 text_off := pe_test_section_header(image, '.text')
918 text_rva := pe_test_u32(image, text_off + 12)
919 text_raw := int(pe_test_u32(image, text_off + 20))
920 entry_stub_len := linker.entry_stub_size()
921
922 assert image[text_raw..text_raw + 4] == [u8(0x48), 0x83, 0xec, 0x28]
923 assert image[text_raw + entry_stub_len - 1] == 0xcc
924
925 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
926 first_import_thunk_rva := pe_test_first_import_thunk_rva(image, text_rva, text_raw,
927
928 entry_stub_len + obj.text_data.len)
929 assert image[text_raw + 4] == 0xe8
930 assert pe_test_text_rel32_target(image, text_rva, text_raw, 5) == text_rva + u32(entry_stub_len)
931 assert image[text_raw + 9] == 0xe8
932 assert pe_test_text_rel32_target(image, text_rva, text_raw, 10) == text_rva +
933 u32(entry_stub_len + 1)
934 assert image[text_raw + 14..text_raw + 16] == [u8(0x31), 0xc9]
935 assert image[text_raw + 16] == 0xe8
936 exit_target := pe_test_text_rel32_target(image, text_rva, text_raw, 17)
937 assert exit_target >= runtime_start_rva
938 assert exit_target < first_import_thunk_rva
939}
940
941fn test_pe_linker_entry_stub_uses_common_runtime_exit_when_atexit_is_present() {
942 mut obj := CoffObject.new()
943 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
944 obj.add_symbol('main', 5, true, 1)
945 atexit_sym := obj.add_undefined('atexit')
946 obj.add_text_reloc(1, atexit_sym, coff_image_rel_amd64_rel32)
947
948 mut linker := PeLinker.new(obj)
949 image := linker.image() or { panic(err) }
950 text_off := pe_test_section_header(image, '.text')
951 text_rva := pe_test_u32(image, text_off + 12)
952 text_raw := int(pe_test_u32(image, text_off + 20))
953 entry_stub_len := linker.entry_stub_size()
954 first_import_thunk_rva := pe_test_first_import_thunk_rva(image, text_rva, text_raw,
955
956 entry_stub_len + obj.text_data.len)
957 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
958
959 assert image[text_raw..text_raw + 4] == [u8(0x48), 0x83, 0xec, 0x28]
960 assert image[text_raw + 4] == 0xe8
961 assert pe_test_text_rel32_target(image, text_rva, text_raw, 5) == text_rva +
962 u32(entry_stub_len + 5)
963 assert image[text_raw + 9..text_raw + 11] == [u8(0x31), 0xc9]
964 assert image[text_raw + 11] == 0xe8
965 exit_target := pe_test_text_rel32_target(image, text_rva, text_raw, 12)
966 assert exit_target >= runtime_start_rva
967 assert exit_target < first_import_thunk_rva
968 assert image[text_raw + entry_stub_len - 1] == 0xcc
969}
970
971fn test_pe_linker_bootstraps_windows_arguments_before_vinit() {
972 obj := sample_pe_arguments_coff_object(true)
973 mut linker := PeLinker.new(obj)
974 image := linker.image() or { panic(err) }
975
976 text_off := pe_test_section_header(image, '.text')
977 text_rva := pe_test_u32(image, text_off + 12)
978 text_raw := int(pe_test_u32(image, text_off + 20))
979 data_off := pe_test_section_header(image, '.data')
980 data_rva := pe_test_u32(image, data_off + 12)
981 entry_stub_len := linker.entry_stub_size()
982 bootstrap_len := linker.windows_argv_bootstrap_size()
983
984 assert bootstrap_len > 0
985 assert image[text_raw..text_raw + 4] == [u8(0x48), 0x83, 0xec, 0x28]
986 assert image[text_raw + 4..text_raw + 12] == [u8(0xc7), 0x44, 0x24, 0x20, 0, 0, 0, 0]
987 assert image[text_raw + 12] == 0xe8
988 assert image[text_raw + 17..text_raw + 20] == [u8(0x48), 0x89, 0xc1]
989 assert image[text_raw + 20..text_raw + 25] == [u8(0x48), 0x8d, 0x54, 0x24, 0x20]
990 assert image[text_raw + 25] == 0xe8
991 assert image[text_raw + 30..text_raw + 33] == [u8(0x48), 0x85, 0xc0]
992 assert image[text_raw + 33..text_raw + 35] == [u8(0x75), 0x0a]
993 assert image[text_raw + 35..text_raw + 40] == [u8(0xb9), 1, 0, 0, 0]
994 assert image[text_raw + 40] == 0xe8
995 assert image[text_raw + 45..text_raw + 48] == [u8(0x4c), 0x8d, 0x15]
996 assert image[text_raw + 52..text_raw + 55] == [u8(0x49), 0x89, 0x02]
997 assert image[text_raw + 55..text_raw + 58] == [u8(0x4c), 0x8d, 0x15]
998 assert image[text_raw + 62..text_raw + 66] == [u8(0x8b), 0x4c, 0x24, 0x20]
999 assert image[text_raw + 66..text_raw + 69] == [u8(0x41), 0x89, 0x0a]
1000
1001 get_command_line_thunk := pe_test_import_thunk_rva(image, text_rva, text_raw, entry_stub_len +
1002 obj.text_data.len, 'GetCommandLineW')
1003 command_line_to_argv_thunk := pe_test_import_thunk_rva(image, text_rva, text_raw,
1004
1005 entry_stub_len + obj.text_data.len, 'CommandLineToArgvW')
1006 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1007 first_import_thunk_rva := pe_test_first_import_thunk_rva(image, text_rva, text_raw,
1008
1009 entry_stub_len + obj.text_data.len)
1010 assert pe_test_text_rel32_target(image, text_rva, text_raw, 13) == get_command_line_thunk
1011 assert pe_test_text_rel32_target(image, text_rva, text_raw, 26) == command_line_to_argv_thunk
1012 argv_failure_exit_target := pe_test_text_rel32_target(image, text_rva, text_raw, 41)
1013 assert argv_failure_exit_target >= runtime_start_rva
1014 assert argv_failure_exit_target < first_import_thunk_rva
1015 assert pe_test_text_rel32_target(image, text_rva, text_raw, 48) == data_rva + 8
1016 assert pe_test_text_rel32_target(image, text_rva, text_raw, 58) == data_rva
1017 vinit_call_field := 4 + bootstrap_len + 1
1018 main_call_field := vinit_call_field + 5
1019 assert pe_test_text_rel32_target(image, text_rva, text_raw, vinit_call_field) == text_rva +
1020 u32(entry_stub_len)
1021 assert pe_test_text_rel32_target(image, text_rva, text_raw, main_call_field) == text_rva +
1022 u32(entry_stub_len + 1)
1023}
1024
1025fn test_pe_linker_applies_internal_rel32_relocations() {
1026 mut obj := CoffObject.new()
1027 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1028 obj.add_symbol('main', 0, true, 1)
1029 helper_sym := obj.add_symbol('helper', 5, true, 1)
1030 obj.add_text_reloc(1, helper_sym, coff_image_rel_amd64_rel32)
1031
1032 mut linker := PeLinker.new(obj)
1033 image := linker.image() or { panic(err) }
1034 text_off := pe_test_section_header(image, '.text')
1035 text_raw := int(pe_test_u32(image, text_off + 20))
1036 field_off := text_raw + linker.entry_stub_size() + 1
1037 assert pe_test_i32(image, field_off) == 0
1038}
1039
1040fn test_pe_linker_applies_import_rel32_relocations_to_thunks() {
1041 mut obj := CoffObject.new()
1042 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1043 obj.add_symbol('main', 0, true, 1)
1044 exit_sym := obj.add_undefined('ExitProcess')
1045 obj.add_text_reloc(1, exit_sym, coff_image_rel_amd64_rel32)
1046
1047 mut linker := PeLinker.new(obj)
1048 image := linker.image() or { panic(err) }
1049 text_off := pe_test_section_header(image, '.text')
1050 text_rva := pe_test_u32(image, text_off + 12)
1051 text_raw := int(pe_test_u32(image, text_off + 20))
1052 field_off := text_raw + linker.entry_stub_size() + 1
1053 field_rva := text_rva + u32(linker.entry_stub_size() + 1)
1054 disp := pe_test_i32(image, field_off)
1055 target := u32(i32(field_rva + 4) + disp)
1056 assert target == pe_test_import_thunk_rva(image, text_rva, text_raw, linker.entry_stub_size() +
1057 obj.text_data.len, 'ExitProcess')
1058}
1059
1060fn test_pe_linker_resolves_get_current_thread_id_as_kernel32_import() {
1061 mut obj := CoffObject.new()
1062 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1063 obj.add_symbol('main', 0, true, 1)
1064 get_tid_sym := obj.add_undefined('GetCurrentThreadId')
1065 obj.add_text_reloc(1, get_tid_sym, coff_image_rel_amd64_rel32)
1066
1067 mut linker := PeLinker.new(obj)
1068 image := linker.image() or { panic(err) }
1069 text_off := pe_test_section_header(image, '.text')
1070 text_rva := pe_test_u32(image, text_off + 12)
1071 text_raw := int(pe_test_u32(image, text_off + 20))
1072 field_off := text_raw + linker.entry_stub_size() + 1
1073 field_rva := text_rva + u32(linker.entry_stub_size() + 1)
1074 disp := pe_test_i32(image, field_off)
1075 target := u32(i32(field_rva + 4) + disp)
1076 get_tid_thunk := pe_test_import_thunk_rva(image, text_rva, text_raw, linker.entry_stub_size() +
1077 obj.text_data.len, 'GetCurrentThreadId')
1078
1079 assert target == get_tid_thunk
1080}
1081
1082fn test_pe_linker_resolves_windows_system_time_imports() {
1083 for name in ['GetSystemTimeAsFileTime', 'FileTimeToSystemTime', 'SystemTimeToTzSpecificLocalTime',
1084 'QueryPerformanceFrequency', 'QueryPerformanceCounter'] {
1085 mut obj := CoffObject.new()
1086 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1087 obj.add_symbol('main', 0, true, 1)
1088 sym := obj.add_undefined(name)
1089 obj.add_text_reloc(1, sym, coff_image_rel_amd64_rel32)
1090
1091 mut linker := PeLinker.new(obj)
1092 image := linker.image() or { panic(err) }
1093 text_off := pe_test_section_header(image, '.text')
1094 text_rva := pe_test_u32(image, text_off + 12)
1095 text_raw := int(pe_test_u32(image, text_off + 20))
1096 field_off := text_raw + linker.entry_stub_size() + 1
1097 field_rva := text_rva + u32(linker.entry_stub_size() + 1)
1098 disp := pe_test_i32(image, field_off)
1099 target := u32(i32(field_rva + 4) + disp)
1100 thunk := pe_test_import_thunk_rva(image, text_rva, text_raw, linker.entry_stub_size() +
1101 obj.text_data.len, name)
1102
1103 assert target == thunk
1104 }
1105}
1106
1107fn test_pe_linker_resolves_memcmp_and_exit_with_internal_runtime_thunks() {
1108 mut obj := CoffObject.new()
1109 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3]
1110 obj.add_symbol('main', 10, true, 1)
1111 memcmp_sym := obj.add_undefined('memcmp')
1112 exit_sym := obj.add_undefined('exit')
1113 obj.add_text_reloc(1, memcmp_sym, coff_image_rel_amd64_rel32)
1114 obj.add_text_reloc(6, exit_sym, coff_image_rel_amd64_rel32)
1115
1116 mut linker := PeLinker.new(obj)
1117 image := linker.image() or { panic(err) }
1118 text_off := pe_test_section_header(image, '.text')
1119 text_rva := pe_test_u32(image, text_off + 12)
1120 text_raw := int(pe_test_u32(image, text_off + 20))
1121 text_raw_size := int(pe_test_u32(image, text_off + 16))
1122 entry_stub_len := linker.entry_stub_size()
1123 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1124
1125 mut first_import_thunk_file_off := -1
1126 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1127 if image[off] == 0xff && image[off + 1] == 0x25 {
1128 first_import_thunk_file_off = off
1129 break
1130 }
1131 }
1132 assert first_import_thunk_file_off > 0
1133 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1134
1135 memcmp_field_off := text_raw + entry_stub_len + 1
1136 memcmp_field_rva := text_rva + u32(entry_stub_len + 1)
1137 memcmp_target := u32(i32(memcmp_field_rva + 4) + pe_test_i32(image, memcmp_field_off))
1138 exit_field_off := text_raw + entry_stub_len + 6
1139 exit_field_rva := text_rva + u32(entry_stub_len + 6)
1140 exit_target := u32(i32(exit_field_rva + 4) + pe_test_i32(image, exit_field_off))
1141
1142 assert memcmp_target >= runtime_start_rva
1143 assert memcmp_target < first_import_thunk_rva
1144 assert exit_target >= runtime_start_rva
1145 assert exit_target < first_import_thunk_rva
1146 assert exit_target != memcmp_target
1147
1148 memcmp_off := pe_test_rva_to_file_off(image, memcmp_target)
1149 assert memcmp_off > 0
1150 assert image[memcmp_off..memcmp_off + 3] == [u8(0x4d), 0x85, 0xc0] // test r8, r8
1151 assert image[memcmp_off + 22..memcmp_off + 25] == [u8(0x49), 0xff, 0xc8] // dec r8
1152}
1153
1154fn test_pe_linker_resolves_errno_with_internal_runtime_data_slot() {
1155 mut obj := CoffObject.new()
1156 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1157 obj.add_symbol('main', 5, true, 1)
1158 errno_sym := obj.add_undefined('_errno')
1159 obj.add_text_reloc(1, errno_sym, coff_image_rel_amd64_rel32)
1160
1161 mut linker := PeLinker.new(obj)
1162 image := linker.image() or { panic(err) }
1163 text_off := pe_test_section_header(image, '.text')
1164 text_rva := pe_test_u32(image, text_off + 12)
1165 text_raw := int(pe_test_u32(image, text_off + 20))
1166 text_raw_size := int(pe_test_u32(image, text_off + 16))
1167 entry_stub_len := linker.entry_stub_size()
1168 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1169
1170 mut first_import_thunk_file_off := -1
1171 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1172 if image[off] == 0xff && image[off + 1] == 0x25 {
1173 first_import_thunk_file_off = off
1174 break
1175 }
1176 }
1177 assert first_import_thunk_file_off > 0
1178 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1179
1180 errno_field_off := text_raw + entry_stub_len + 1
1181 errno_field_rva := text_rva + u32(entry_stub_len + 1)
1182 errno_target := u32(i32(errno_field_rva + 4) + pe_test_i32(image, errno_field_off))
1183 assert errno_target >= runtime_start_rva
1184 assert errno_target < first_import_thunk_rva
1185
1186 errno_off := pe_test_rva_to_file_off(image, errno_target)
1187 assert errno_off > 0
1188 assert image[errno_off..errno_off + 3] == [u8(0x48), 0x8d, 0x05] // lea rax, [rip + disp32]
1189 assert image[errno_off + 7] == u8(0xc3) // ret
1190 slot_target := u32(i32(errno_target + 7) + pe_test_i32(image, errno_off + 3))
1191 data_off := pe_test_section_header(image, '.data')
1192 assert data_off > 0
1193 data_rva := pe_test_u32(image, data_off + 12)
1194 data_raw_size := pe_test_u32(image, data_off + 16)
1195 assert slot_target >= data_rva
1196 assert slot_target < data_rva + data_raw_size
1197 slot_off := pe_test_rva_to_file_off(image, slot_target)
1198 assert pe_test_u32(image, slot_off) == 0
1199}
1200
1201fn test_pe_linker_resolves_malloc_with_internal_nonzeroing_heap_thunk() {
1202 mut obj := CoffObject.new()
1203 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1204 obj.add_symbol('main', 5, true, 1)
1205 malloc_sym := obj.add_undefined('malloc')
1206 obj.add_text_reloc(1, malloc_sym, coff_image_rel_amd64_rel32)
1207
1208 mut linker := PeLinker.new(obj)
1209 image := linker.image() or { panic(err) }
1210 names := pe_test_import_names(image)
1211 dll_names := pe_test_import_dll_names(image)
1212 assert names == ['ExitProcess', 'GetProcessHeap', 'HeapAlloc']
1213 assert dll_names == ['kernel32.dll']
1214
1215 text_off := pe_test_section_header(image, '.text')
1216 text_rva := pe_test_u32(image, text_off + 12)
1217 text_raw := int(pe_test_u32(image, text_off + 20))
1218 text_raw_size := int(pe_test_u32(image, text_off + 16))
1219 entry_stub_len := linker.entry_stub_size()
1220 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1221
1222 mut first_import_thunk_file_off := -1
1223 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1224 if image[off] == 0xff && image[off + 1] == 0x25 {
1225 first_import_thunk_file_off = off
1226 break
1227 }
1228 }
1229 assert first_import_thunk_file_off > 0
1230 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1231
1232 malloc_target := pe_test_text_rel32_target(image, text_rva, text_raw, entry_stub_len + 1)
1233 assert malloc_target >= runtime_start_rva
1234 assert malloc_target < first_import_thunk_rva
1235
1236 malloc_off := pe_test_rva_to_file_off(image, malloc_target)
1237 assert malloc_off > 0
1238 mut has_shadow_space_frame := false
1239 mut has_size_saved_from_rcx := false
1240 mut has_heap_zero_memory_flag := false
1241 mut has_plain_heap_flags := false
1242 mut has_rdx_alignment_dependency := false
1243 mut has_aligned_allocation_padding := false
1244 mut has_aligned_cookie_store := false
1245 mut has_aligned_return_pointer := false
1246 runtime_scan_end := first_import_thunk_file_off
1247 for off in malloc_off .. runtime_scan_end - 4 {
1248 if image[off..off + 4] == [u8(0x48), 0x83, 0xec, 0x28] {
1249 has_shadow_space_frame = true
1250 }
1251 if image[off..off + 5] == [u8(0x48), 0x89, 0x4c, 0x24, 0x20] {
1252 has_size_saved_from_rcx = true
1253 }
1254 if image[off..off + 5] == [u8(0xba), 0x08, 0, 0, 0] {
1255 has_heap_zero_memory_flag = true
1256 }
1257 if image[off..off + 2] == [u8(0x31), 0xd2] {
1258 has_plain_heap_flags = true
1259 }
1260 if image[off..off + 4] == [u8(0x48), 0x83, 0xfa, 0x10] {
1261 has_rdx_alignment_dependency = true
1262 }
1263 if image[off..off + 4] == [u8(0x49), 0x83, 0xc0, 0x18] {
1264 has_aligned_allocation_padding = true
1265 }
1266 if image[off..off + 3] == [u8(0x4c), 0x89, 0xd8] {
1267 has_aligned_return_pointer = true
1268 }
1269 }
1270 for off in malloc_off .. runtime_scan_end - 14 {
1271 if image[off..off + 15] == [
1272 u8(0x49),
1273 0x89,
1274 0xc3,
1275 0x49,
1276 0x83,
1277 0xc3,
1278 0x17,
1279 0x49,
1280 0x83,
1281 0xe3,
1282 0xf0,
1283 0x49,
1284 0x89,
1285 0x43,
1286 0xf8,
1287 ] {
1288 has_aligned_cookie_store = true
1289 }
1290 }
1291 assert has_shadow_space_frame
1292 assert has_size_saved_from_rcx
1293 assert !has_heap_zero_memory_flag
1294 assert has_plain_heap_flags
1295 assert !has_rdx_alignment_dependency
1296 assert has_aligned_allocation_padding
1297 assert has_aligned_cookie_store
1298 assert has_aligned_return_pointer
1299}
1300
1301fn test_pe_linker_resolves_malloc_and_free_with_compatible_heap_cookie() {
1302 mut obj := CoffObject.new()
1303 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3]
1304 obj.add_symbol('main', 10, true, 1)
1305 malloc_sym := obj.add_undefined('malloc')
1306 free_sym := obj.add_undefined('free')
1307 obj.add_text_reloc(1, malloc_sym, coff_image_rel_amd64_rel32)
1308 obj.add_text_reloc(6, free_sym, coff_image_rel_amd64_rel32)
1309
1310 mut linker := PeLinker.new(obj)
1311 image := linker.image() or { panic(err) }
1312 names := pe_test_import_names(image)
1313 dll_names := pe_test_import_dll_names(image)
1314 assert names == ['ExitProcess', 'GetProcessHeap', 'HeapAlloc', 'HeapFree']
1315 assert dll_names == ['kernel32.dll']
1316 assert 'HeapReAlloc' !in names
1317 assert 'ucrtbase.dll' !in dll_names
1318 assert 'msvcrt.dll' !in dll_names
1319
1320 text_off := pe_test_section_header(image, '.text')
1321 text_rva := pe_test_u32(image, text_off + 12)
1322 text_raw := int(pe_test_u32(image, text_off + 20))
1323 text_raw_size := int(pe_test_u32(image, text_off + 16))
1324 entry_stub_len := linker.entry_stub_size()
1325 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1326
1327 mut first_import_thunk_file_off := -1
1328 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1329 if image[off] == 0xff && image[off + 1] == 0x25 {
1330 first_import_thunk_file_off = off
1331 break
1332 }
1333 }
1334 assert first_import_thunk_file_off > 0
1335 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1336
1337 malloc_target := pe_test_text_rel32_target(image, text_rva, text_raw, entry_stub_len + 1)
1338 free_target := pe_test_text_rel32_target(image, text_rva, text_raw, entry_stub_len + 6)
1339 assert malloc_target >= runtime_start_rva
1340 assert malloc_target < first_import_thunk_rva
1341 assert free_target >= runtime_start_rva
1342 assert free_target < first_import_thunk_rva
1343
1344 malloc_off := pe_test_rva_to_file_off(image, malloc_target)
1345 free_off := pe_test_rva_to_file_off(image, free_target)
1346 assert malloc_off > 0
1347 assert free_off > 0
1348 mut malloc_stores_raw_cookie := false
1349 for off in malloc_off .. first_import_thunk_file_off - 14 {
1350 if image[off..off + 15] == [
1351 u8(0x49),
1352 0x89,
1353 0xc3,
1354 0x49,
1355 0x83,
1356 0xc3,
1357 0x17,
1358 0x49,
1359 0x83,
1360 0xe3,
1361 0xf0,
1362 0x49,
1363 0x89,
1364 0x43,
1365 0xf8,
1366 ] {
1367 malloc_stores_raw_cookie = true
1368 }
1369 }
1370 assert malloc_stores_raw_cookie
1371 mut free_reads_raw_cookie := false
1372 for off in free_off .. first_import_thunk_file_off - 3 {
1373 if image[off..off + 4] == [u8(0x48), 0x8b, 0x41, 0xf8] {
1374 free_reads_raw_cookie = true
1375 }
1376 }
1377 assert free_reads_raw_cookie
1378}
1379
1380fn test_pe_linker_resolves_calloc_with_internal_zeroed_heap_thunk() {
1381 mut obj := CoffObject.new()
1382 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1383 obj.add_symbol('main', 5, true, 1)
1384 calloc_sym := obj.add_undefined('calloc')
1385 obj.add_text_reloc(1, calloc_sym, coff_image_rel_amd64_rel32)
1386
1387 mut linker := PeLinker.new(obj)
1388 image := linker.image() or { panic(err) }
1389 text_off := pe_test_section_header(image, '.text')
1390 text_rva := pe_test_u32(image, text_off + 12)
1391 text_raw := int(pe_test_u32(image, text_off + 20))
1392 text_raw_size := int(pe_test_u32(image, text_off + 16))
1393 entry_stub_len := linker.entry_stub_size()
1394 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1395
1396 mut first_import_thunk_file_off := -1
1397 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1398 if image[off] == 0xff && image[off + 1] == 0x25 {
1399 first_import_thunk_file_off = off
1400 break
1401 }
1402 }
1403 assert first_import_thunk_file_off > 0
1404 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1405
1406 calloc_field_off := text_raw + entry_stub_len + 1
1407 calloc_field_rva := text_rva + u32(entry_stub_len + 1)
1408 calloc_target := u32(i32(calloc_field_rva + 4) + pe_test_i32(image, calloc_field_off))
1409 assert calloc_target >= runtime_start_rva
1410 assert calloc_target < first_import_thunk_rva
1411
1412 calloc_off := pe_test_rva_to_file_off(image, calloc_target)
1413 assert calloc_off > 0
1414 assert image[calloc_off..calloc_off + 3] == [u8(0x48), 0x89, 0xc8] // mov rax, rcx
1415 assert image[calloc_off + 3..calloc_off + 6] == [u8(0x48), 0xf7, 0xe2] // mul rdx
1416 mut has_heap_zero_memory_flag := false
1417 mut has_aligned_allocation_padding := false
1418 mut has_aligned_cookie_store := false
1419 mut has_aligned_return_pointer := false
1420 runtime_scan_end := first_import_thunk_file_off - 16
1421 for off in calloc_off .. runtime_scan_end {
1422 if image[off..off + 5] == [u8(0xba), 0x08, 0, 0, 0] {
1423 has_heap_zero_memory_flag = true
1424 }
1425 if image[off..off + 4] == [u8(0x49), 0x83, 0xc0, 0x18] {
1426 has_aligned_allocation_padding = true
1427 }
1428 if image[off..off + 15] == [
1429 u8(0x49),
1430 0x89,
1431 0xc3,
1432 0x49,
1433 0x83,
1434 0xc3,
1435 0x17,
1436 0x49,
1437 0x83,
1438 0xe3,
1439 0xf0,
1440 0x49,
1441 0x89,
1442 0x43,
1443 0xf8,
1444 ] {
1445 has_aligned_cookie_store = true
1446 }
1447 if image[off..off + 3] == [u8(0x4c), 0x89, 0xd8] {
1448 has_aligned_return_pointer = true
1449 }
1450 }
1451 assert has_heap_zero_memory_flag
1452 assert has_aligned_allocation_padding
1453 assert has_aligned_cookie_store
1454 assert has_aligned_return_pointer
1455}
1456
1457fn test_pe_linker_resolves_aligned_malloc_checks_size_overflow_before_heap_alloc() {
1458 mut obj := CoffObject.new()
1459 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1460 obj.add_symbol('main', 5, true, 1)
1461 malloc_sym := obj.add_undefined('_aligned_malloc')
1462 obj.add_text_reloc(1, malloc_sym, coff_image_rel_amd64_rel32)
1463
1464 mut linker := PeLinker.new(obj)
1465 image := linker.image() or { panic(err) }
1466 text_off := pe_test_section_header(image, '.text')
1467 text_rva := pe_test_u32(image, text_off + 12)
1468 text_raw := int(pe_test_u32(image, text_off + 20))
1469 text_raw_size := int(pe_test_u32(image, text_off + 16))
1470 entry_stub_len := linker.entry_stub_size()
1471 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1472
1473 mut first_import_thunk_file_off := -1
1474 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1475 if image[off] == 0xff && image[off + 1] == 0x25 {
1476 first_import_thunk_file_off = off
1477 break
1478 }
1479 }
1480 assert first_import_thunk_file_off > 0
1481 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1482
1483 malloc_field_off := text_raw + entry_stub_len + 1
1484 malloc_field_rva := text_rva + u32(entry_stub_len + 1)
1485 malloc_target := u32(i32(malloc_field_rva + 4) + pe_test_i32(image, malloc_field_off))
1486 assert malloc_target >= runtime_start_rva
1487 assert malloc_target < first_import_thunk_rva
1488
1489 malloc_off := pe_test_rva_to_file_off(image, malloc_target)
1490 assert malloc_off > 0
1491 mut size_overflow_check_off := -1
1492 mut padding_overflow_check_off := -1
1493 mut heap_alloc_call_off := -1
1494 mut alloc_failed_check_off := -1
1495 runtime_scan_end := first_import_thunk_file_off - 16
1496 for off in malloc_off .. runtime_scan_end {
1497 if image[off..off + 4] == [u8(0x4d), 0x01, 0xc8, 0x72] {
1498 size_overflow_check_off = off + 3
1499 }
1500 if image[off..off + 5] == [u8(0x49), 0x83, 0xc0, 0x08, 0x72] {
1501 padding_overflow_check_off = off + 4
1502 heap_alloc_call_off = off + 6
1503 }
1504 if image[off..off + 4] == [u8(0x48), 0x85, 0xc0, 0x74] {
1505 alloc_failed_check_off = off + 3
1506 }
1507 }
1508 assert size_overflow_check_off >= 0
1509 assert padding_overflow_check_off >= 0
1510 assert heap_alloc_call_off >= 0
1511 assert alloc_failed_check_off >= 0
1512 assert image[heap_alloc_call_off] == 0xe8
1513 assert size_overflow_check_off < padding_overflow_check_off
1514 assert padding_overflow_check_off < heap_alloc_call_off
1515 assert heap_alloc_call_off < alloc_failed_check_off
1516 size_overflow_target := pe_test_rel8_target(size_overflow_check_off, image[
1517 size_overflow_check_off + 1])
1518 padding_overflow_target := pe_test_rel8_target(padding_overflow_check_off, image[
1519 padding_overflow_check_off + 1])
1520 alloc_failed_target := pe_test_rel8_target(alloc_failed_check_off, image[
1521 alloc_failed_check_off + 1])
1522 assert size_overflow_target == padding_overflow_target
1523 assert size_overflow_target == alloc_failed_target
1524 assert alloc_failed_check_off < alloc_failed_target
1525 assert alloc_failed_target < first_import_thunk_file_off
1526}
1527
1528fn test_pe_linker_resolves_strlen_with_internal_runtime_thunk() {
1529 mut obj := CoffObject.new()
1530 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1531 obj.add_symbol('main', 5, true, 1)
1532 strlen_sym := obj.add_undefined('strlen')
1533 obj.add_text_reloc(1, strlen_sym, coff_image_rel_amd64_rel32)
1534
1535 mut linker := PeLinker.new(obj)
1536 image := linker.image() or { panic(err) }
1537 text_off := pe_test_section_header(image, '.text')
1538 text_rva := pe_test_u32(image, text_off + 12)
1539 text_raw := int(pe_test_u32(image, text_off + 20))
1540 text_raw_size := int(pe_test_u32(image, text_off + 16))
1541 entry_stub_len := linker.entry_stub_size()
1542 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1543
1544 mut first_import_thunk_file_off := -1
1545 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1546 if image[off] == 0xff && image[off + 1] == 0x25 {
1547 first_import_thunk_file_off = off
1548 break
1549 }
1550 }
1551 assert first_import_thunk_file_off > 0
1552 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1553
1554 strlen_field_off := text_raw + entry_stub_len + 1
1555 strlen_field_rva := text_rva + u32(entry_stub_len + 1)
1556 strlen_target := u32(i32(strlen_field_rva + 4) + pe_test_i32(image, strlen_field_off))
1557 assert strlen_target >= runtime_start_rva
1558 assert strlen_target < first_import_thunk_rva
1559
1560 strlen_off := pe_test_rva_to_file_off(image, strlen_target)
1561 assert strlen_off > 0
1562 assert image[strlen_off..strlen_off + 3] == [u8(0x48), 0x89, 0xc8] // mov rax, rcx
1563 assert image[strlen_off + 3..strlen_off + 6] == [u8(0x80), 0x38, 0x00] // cmp byte ptr [rax], 0
1564}
1565
1566fn test_pe_linker_resolves_wcslen_with_internal_runtime_thunk() {
1567 mut obj := CoffObject.new()
1568 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1569 obj.add_symbol('main', 5, true, 1)
1570 wcslen_sym := obj.add_undefined('wcslen')
1571 obj.add_text_reloc(1, wcslen_sym, coff_image_rel_amd64_rel32)
1572
1573 mut linker := PeLinker.new(obj)
1574 image := linker.image() or { panic(err) }
1575 text_off := pe_test_section_header(image, '.text')
1576 text_rva := pe_test_u32(image, text_off + 12)
1577 text_raw := int(pe_test_u32(image, text_off + 20))
1578 text_raw_size := int(pe_test_u32(image, text_off + 16))
1579 entry_stub_len := linker.entry_stub_size()
1580 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1581
1582 mut first_import_thunk_file_off := -1
1583 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1584 if image[off] == 0xff && image[off + 1] == 0x25 {
1585 first_import_thunk_file_off = off
1586 break
1587 }
1588 }
1589 assert first_import_thunk_file_off > 0
1590 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1591
1592 wcslen_field_off := text_raw + entry_stub_len + 1
1593 wcslen_field_rva := text_rva + u32(entry_stub_len + 1)
1594 wcslen_target := u32(i32(wcslen_field_rva + 4) + pe_test_i32(image, wcslen_field_off))
1595 assert wcslen_target >= runtime_start_rva
1596 assert wcslen_target < first_import_thunk_rva
1597
1598 wcslen_off := pe_test_rva_to_file_off(image, wcslen_target)
1599 assert wcslen_off > 0
1600 assert image[wcslen_off..wcslen_off + 3] == [u8(0x48), 0x89, 0xc8] // mov rax, rcx
1601 assert image[wcslen_off + 3..wcslen_off + 7] == [u8(0x66), 0x83, 0x38, 0x00] // cmp word ptr [rax], 0
1602 assert image[wcslen_off + 15..wcslen_off + 18] == [u8(0x48), 0x29, 0xc8] // sub rax, rcx
1603 assert image[wcslen_off + 18..wcslen_off + 21] == [u8(0x48), 0xd1, 0xe8] // shr rax, 1
1604}
1605
1606fn test_pe_linker_resolves_wgetcwd_with_kernel32_runtime_thunk() {
1607 mut obj := CoffObject.new()
1608 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1609 obj.add_symbol('main', 5, true, 1)
1610 wgetcwd_sym := obj.add_undefined('_wgetcwd')
1611 obj.add_text_reloc(1, wgetcwd_sym, coff_image_rel_amd64_rel32)
1612
1613 mut linker := PeLinker.new(obj)
1614 image := linker.image() or { panic(err) }
1615 text_off := pe_test_section_header(image, '.text')
1616 text_rva := pe_test_u32(image, text_off + 12)
1617 text_raw := int(pe_test_u32(image, text_off + 20))
1618 text_raw_size := int(pe_test_u32(image, text_off + 16))
1619 entry_stub_len := linker.entry_stub_size()
1620 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1621
1622 mut first_import_thunk_file_off := -1
1623 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1624 if image[off] == 0xff && image[off + 1] == 0x25 {
1625 first_import_thunk_file_off = off
1626 break
1627 }
1628 }
1629 assert first_import_thunk_file_off > 0
1630 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1631
1632 wgetcwd_field_off := text_raw + entry_stub_len + 1
1633 wgetcwd_field_rva := text_rva + u32(entry_stub_len + 1)
1634 wgetcwd_target := u32(i32(wgetcwd_field_rva + 4) + pe_test_i32(image, wgetcwd_field_off))
1635 assert wgetcwd_target >= runtime_start_rva
1636 assert wgetcwd_target < first_import_thunk_rva
1637
1638 wgetcwd_off := pe_test_rva_to_file_off(image, wgetcwd_target)
1639 assert wgetcwd_off > 0
1640 assert image[wgetcwd_off..wgetcwd_off + 4] == [u8(0x48), 0x83, 0xec, 0x38] // sub rsp, 56
1641 assert image[wgetcwd_off + 13..wgetcwd_off + 15] == [u8(0x89), 0xd1] // mov ecx, edx
1642 assert image[wgetcwd_off + 15..wgetcwd_off + 20] == [
1643 u8(0x48),
1644 0x8b,
1645 0x54,
1646 0x24,
1647 0x20,
1648 ] // mov rdx, [rsp+32]
1649}
1650
1651fn test_pe_linker_resolves_wgetenv_with_kernel32_runtime_thunk() {
1652 mut obj := CoffObject.new()
1653 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1654 obj.add_symbol('main', 5, true, 1)
1655 wgetenv_sym := obj.add_undefined('_wgetenv')
1656 obj.add_text_reloc(1, wgetenv_sym, coff_image_rel_amd64_rel32)
1657
1658 mut linker := PeLinker.new(obj)
1659 image := linker.image() or { panic(err) }
1660 text_off := pe_test_section_header(image, '.text')
1661 text_rva := pe_test_u32(image, text_off + 12)
1662 text_raw := int(pe_test_u32(image, text_off + 20))
1663 text_raw_size := int(pe_test_u32(image, text_off + 16))
1664 entry_stub_len := linker.entry_stub_size()
1665 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1666
1667 mut first_import_thunk_file_off := -1
1668 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1669 if image[off] == 0xff && image[off + 1] == 0x25 {
1670 first_import_thunk_file_off = off
1671 break
1672 }
1673 }
1674 assert first_import_thunk_file_off > 0
1675 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1676
1677 wgetenv_field_off := text_raw + entry_stub_len + 1
1678 wgetenv_field_rva := text_rva + u32(entry_stub_len + 1)
1679 wgetenv_target := u32(i32(wgetenv_field_rva + 4) + pe_test_i32(image, wgetenv_field_off))
1680 assert wgetenv_target >= runtime_start_rva
1681 assert wgetenv_target < first_import_thunk_rva
1682
1683 wgetenv_off := pe_test_rva_to_file_off(image, wgetenv_target)
1684 assert wgetenv_off > 0
1685 assert image[wgetenv_off..wgetenv_off + 4] == [u8(0x48), 0x83, 0xec, 0x28] // sub rsp, 40
1686 data_off := pe_test_section_header(image, '.data')
1687 assert data_off > 0
1688 data_rva := pe_test_u32(image, data_off + 12)
1689 data_size := pe_test_u32(image, data_off + 8)
1690 assert data_size >= u32(pe_wgetenv_buffer_bytes)
1691
1692 runtime_text_size := first_import_thunk_file_off - (text_raw + entry_stub_len +
1693 obj.text_data.len)
1694 import_offsets := linker.import_thunk_offsets(runtime_text_size)
1695 names := pe_test_import_names(image)
1696 assert names == ['ExitProcess', 'GetEnvironmentVariableW']
1697 getenv_thunk := import_offsets[pe_kernel32_import_key('GetEnvironmentVariableW')] or {
1698 panic('missing GetEnvironmentVariableW import thunk')
1699 }
1700 getenv_thunk_rva := text_rva + getenv_thunk
1701
1702 mut has_buffer_arg_lea := false
1703 mut has_buffer_return_lea := false
1704 mut has_wgetenv_buffer_size := false
1705 mut calls_getenv := false
1706 runtime_scan_end := first_import_thunk_file_off - 8
1707 for off in wgetenv_off .. runtime_scan_end {
1708 if image[off..off + 3] == [u8(0x48), 0x8d, 0x15] {
1709 field_rva := text_rva + u32(off - text_raw + 3)
1710 target := u32(i32(field_rva + 4) + pe_test_i32(image, off + 3))
1711 if target >= data_rva && target < data_rva + data_size {
1712 has_buffer_arg_lea = true
1713 }
1714 }
1715 if image[off..off + 3] == [u8(0x48), 0x8d, 0x05] {
1716 field_rva := text_rva + u32(off - text_raw + 3)
1717 target := u32(i32(field_rva + 4) + pe_test_i32(image, off + 3))
1718 if target >= data_rva && target < data_rva + data_size {
1719 has_buffer_return_lea = true
1720 }
1721 }
1722 if image[off..off + 6] == [u8(0x41), 0xb8, 0x00, 0x80, 0x00, 0x00] {
1723 has_wgetenv_buffer_size = true
1724 }
1725 if image[off] == u8(0xe8) {
1726 field_rva := text_rva + u32(off - text_raw + 1)
1727 target := u32(i32(field_rva + 4) + pe_test_i32(image, off + 1))
1728 if target == getenv_thunk_rva {
1729 calls_getenv = true
1730 }
1731 }
1732 }
1733 assert has_buffer_arg_lea
1734 assert has_buffer_return_lea
1735 assert has_wgetenv_buffer_size
1736 assert calls_getenv
1737 assert 'GetProcessHeap' !in names
1738 assert 'HeapAlloc' !in names
1739 assert 'HeapFree' !in names
1740}
1741
1742fn test_pe_linker_resolves_aligned_realloc_with_internal_heap_thunk() {
1743 mut obj := CoffObject.new()
1744 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1745 obj.add_symbol('main', 5, true, 1)
1746 realloc_sym := obj.add_undefined('_aligned_realloc')
1747 obj.add_text_reloc(1, realloc_sym, coff_image_rel_amd64_rel32)
1748
1749 mut linker := PeLinker.new(obj)
1750 image := linker.image() or { panic(err) }
1751 text_off := pe_test_section_header(image, '.text')
1752 text_rva := pe_test_u32(image, text_off + 12)
1753 text_raw := int(pe_test_u32(image, text_off + 20))
1754 text_raw_size := int(pe_test_u32(image, text_off + 16))
1755 entry_stub_len := linker.entry_stub_size()
1756 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1757
1758 mut first_import_thunk_file_off := -1
1759 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1760 if image[off] == 0xff && image[off + 1] == 0x25 {
1761 first_import_thunk_file_off = off
1762 break
1763 }
1764 }
1765 assert first_import_thunk_file_off > 0
1766 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1767
1768 realloc_field_off := text_raw + entry_stub_len + 1
1769 realloc_field_rva := text_rva + u32(entry_stub_len + 1)
1770 realloc_target := u32(i32(realloc_field_rva + 4) + pe_test_i32(image, realloc_field_off))
1771 assert realloc_target >= runtime_start_rva
1772 assert realloc_target < first_import_thunk_rva
1773
1774 realloc_off := pe_test_rva_to_file_off(image, realloc_target)
1775 assert realloc_off > 0
1776 assert image[realloc_off..realloc_off + 4] == [u8(0x49), 0x83, 0xf8, 0x10] // cmp r8, 16
1777 assert image[realloc_off + 9..realloc_off + 12] == [u8(0x48), 0x85, 0xc9] // test rcx, rcx
1778}
1779
1780fn test_pe_linker_resolves_free_with_internal_heap_thunk() {
1781 mut obj := CoffObject.new()
1782 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1783 obj.add_symbol('main', 5, true, 1)
1784 free_sym := obj.add_undefined('free')
1785 obj.add_text_reloc(1, free_sym, coff_image_rel_amd64_rel32)
1786
1787 mut linker := PeLinker.new(obj)
1788 image := linker.image() or { panic(err) }
1789 text_off := pe_test_section_header(image, '.text')
1790 text_rva := pe_test_u32(image, text_off + 12)
1791 text_raw := int(pe_test_u32(image, text_off + 20))
1792 text_raw_size := int(pe_test_u32(image, text_off + 16))
1793 entry_stub_len := linker.entry_stub_size()
1794 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1795
1796 mut first_import_thunk_file_off := -1
1797 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1798 if image[off] == 0xff && image[off + 1] == 0x25 {
1799 first_import_thunk_file_off = off
1800 break
1801 }
1802 }
1803 assert first_import_thunk_file_off > 0
1804 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1805
1806 free_field_off := text_raw + entry_stub_len + 1
1807 free_field_rva := text_rva + u32(entry_stub_len + 1)
1808 free_target := u32(i32(free_field_rva + 4) + pe_test_i32(image, free_field_off))
1809 assert free_target >= runtime_start_rva
1810 assert free_target < first_import_thunk_rva
1811
1812 free_off := pe_test_rva_to_file_off(image, free_target)
1813 assert free_off > 0
1814 assert image[free_off..free_off + 4] == [u8(0x48), 0x85, 0xc9, 0x74] // test rcx, rcx; je
1815 assert image[free_off + 5..free_off + 9] == [u8(0x48), 0x83, 0xec, 0x28] // sub rsp, 40
1816 assert image[free_off + 9..free_off + 14] == [
1817 u8(0x48),
1818 0x8b,
1819 0x41,
1820 0xf8,
1821 0x48,
1822 ] // mov rax, [rcx-8]; ...
1823}
1824
1825fn test_pe_linker_resolves_array_rune_string_with_internal_runtime_thunk() {
1826 mut obj := CoffObject.new()
1827 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1828 obj.add_symbol('main', 5, true, 1)
1829 array_rune_string_sym := obj.add_undefined('builtin__Array_rune__string')
1830 obj.add_text_reloc(1, array_rune_string_sym, coff_image_rel_amd64_rel32)
1831
1832 mut linker := PeLinker.new(obj)
1833 image := linker.image() or { panic(err) }
1834 text_off := pe_test_section_header(image, '.text')
1835 text_rva := pe_test_u32(image, text_off + 12)
1836 text_raw := int(pe_test_u32(image, text_off + 20))
1837 text_raw_size := int(pe_test_u32(image, text_off + 16))
1838 entry_stub_len := linker.entry_stub_size()
1839 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1840
1841 mut first_import_thunk_file_off := -1
1842 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1843 if image[off] == 0xff && image[off + 1] == 0x25 {
1844 first_import_thunk_file_off = off
1845 break
1846 }
1847 }
1848 assert first_import_thunk_file_off > 0
1849 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1850
1851 array_rune_string_field_off := text_raw + entry_stub_len + 1
1852 array_rune_string_field_rva := text_rva + u32(entry_stub_len + 1)
1853 array_rune_string_target := u32(i32(array_rune_string_field_rva + 4) +
1854 pe_test_i32(image, array_rune_string_field_off))
1855 assert array_rune_string_target >= runtime_start_rva
1856 assert array_rune_string_target < first_import_thunk_rva
1857
1858 array_rune_string_off := pe_test_rva_to_file_off(image, array_rune_string_target)
1859 assert array_rune_string_off > 0
1860 assert image[array_rune_string_off..array_rune_string_off + 4] == [
1861 u8(0x48),
1862 0x83,
1863 0xec,
1864 0x58,
1865 ] // sub rsp, 88
1866 assert image[array_rune_string_off + 4..array_rune_string_off + 9] == [
1867 u8(0x48),
1868 0x89,
1869 0x4c,
1870 0x24,
1871 0x20,
1872 ] // mov [rsp+32], rcx
1873 mut has_array_len_load := false
1874 mut has_array_data_load := false
1875 mut has_array_offset_load := false
1876 mut has_array_data_offset_add := false
1877 mut has_array_cap_load_as_len := false
1878 mut has_aligned_allocation_padding := false
1879 mut has_aligned_string_cookie_store := false
1880 mut has_raw_heap_pointer_saved_as_string := false
1881 mut has_string_len_store := false
1882 mut has_string_lit_store := false
1883 mut has_out_of_bounds_string_store := false
1884 runtime_scan_end := first_import_thunk_file_off - 24
1885 for off in array_rune_string_off .. runtime_scan_end {
1886 if image[off..off + 4] == [u8(0x48), 0x63, 0x42, 0x0c] {
1887 has_array_len_load = true
1888 }
1889 if image[off..off + 4] == [u8(0x48), 0x63, 0x42, 0x10] {
1890 has_array_cap_load_as_len = true
1891 }
1892 if image[off..off + 3] == [u8(0x48), 0x8b, 0x02] {
1893 has_array_data_load = true
1894 }
1895 if image[off..off + 4] == [u8(0x4c), 0x63, 0x52, 0x08] {
1896 has_array_offset_load = true
1897 }
1898 if image[off..off + 3] == [u8(0x4c), 0x01, 0xd0] {
1899 has_array_data_offset_add = true
1900 }
1901 if image[off..off + 4] == [u8(0x49), 0x83, 0xc0, 0x18] {
1902 has_aligned_allocation_padding = true
1903 }
1904 if image[off..off + 20] == [
1905 u8(0x49),
1906 0x89,
1907 0xc3,
1908 0x49,
1909 0x83,
1910 0xc3,
1911 0x17,
1912 0x49,
1913 0x83,
1914 0xe3,
1915 0xf0,
1916 0x49,
1917 0x89,
1918 0x43,
1919 0xf8,
1920 0x4c,
1921 0x89,
1922 0x5c,
1923 0x24,
1924 0x40,
1925 ] {
1926 has_aligned_string_cookie_store = true
1927 }
1928 if image[off..off + 8] == [u8(0x48), 0x89, 0x44, 0x24, 0x40, 0x49, 0x89, 0xc3] {
1929 has_raw_heap_pointer_saved_as_string = true
1930 }
1931 if image[off..off + 3] == [u8(0x89), 0x51, 0x08] {
1932 has_string_len_store = true
1933 }
1934 if image[off..off + 7] == [u8(0xc7), 0x41, 0x0c, 0, 0, 0, 0] {
1935 has_string_lit_store = true
1936 }
1937 if image[off..off + 8] == [u8(0x48), 0xc7, 0x41, 0x10, 0, 0, 0, 0] {
1938 has_out_of_bounds_string_store = true
1939 }
1940 }
1941 assert has_array_len_load
1942 assert !has_array_cap_load_as_len
1943 assert has_array_data_load
1944 assert has_array_offset_load
1945 assert has_array_data_offset_add
1946 assert has_aligned_allocation_padding
1947 assert has_aligned_string_cookie_store
1948 assert !has_raw_heap_pointer_saved_as_string
1949 assert has_string_len_store
1950 assert has_string_lit_store
1951 assert !has_out_of_bounds_string_store
1952}
1953
1954fn test_pe_linker_resolves_new_array_from_c_array_noscan_with_internal_heap_thunk() {
1955 mut obj := CoffObject.new()
1956 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1957 obj.add_symbol('main', 5, true, 1)
1958 new_array_sym := obj.add_undefined('builtin__new_array_from_c_array_noscan')
1959 obj.add_text_reloc(1, new_array_sym, coff_image_rel_amd64_rel32)
1960
1961 mut linker := PeLinker.new(obj)
1962 image := linker.image() or { panic(err) }
1963 text_off := pe_test_section_header(image, '.text')
1964 text_rva := pe_test_u32(image, text_off + 12)
1965 text_raw := int(pe_test_u32(image, text_off + 20))
1966 text_raw_size := int(pe_test_u32(image, text_off + 16))
1967 entry_stub_len := linker.entry_stub_size()
1968 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
1969
1970 mut first_import_thunk_file_off := -1
1971 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
1972 if image[off] == 0xff && image[off + 1] == 0x25 {
1973 first_import_thunk_file_off = off
1974 break
1975 }
1976 }
1977 assert first_import_thunk_file_off > 0
1978 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
1979
1980 new_array_field_off := text_raw + entry_stub_len + 1
1981 new_array_field_rva := text_rva + u32(entry_stub_len + 1)
1982 new_array_target := u32(i32(new_array_field_rva + 4) + pe_test_i32(image, new_array_field_off))
1983 assert new_array_target >= runtime_start_rva
1984 assert new_array_target < first_import_thunk_rva
1985
1986 new_array_off := pe_test_rva_to_file_off(image, new_array_target)
1987 assert new_array_off > 0
1988 assert image[new_array_off..new_array_off + 4] == [u8(0x48), 0x83, 0xec, 0x58] // sub rsp, 88
1989 mut has_stack_c_array_arg_load := false
1990 mut has_heap_zero_memory_flag := false
1991 mut has_managed_array_allocation_padding_size := false
1992 mut has_header_align_rounding := false
1993 mut has_header_align_mask := false
1994 mut has_cookie_before_array_header := false
1995 mut has_array_header_false_store := false
1996 mut has_data_after_array_header_store := false
1997 mut has_copy_to_data_after_array_header := false
1998 mut has_cookie_over_array_header := false
1999 mut has_extra_header_skip_after_cookie := false
2000 mut has_naive_heap_plus_header_data := false
2001 mut has_noscan_flags_store := false
2002 mut has_element_size_store := false
2003 mut has_byte_copy_loop := false
2004 runtime_scan_end := first_import_thunk_file_off - 16
2005 for off in new_array_off .. runtime_scan_end {
2006 if image[off..off + 8] == [u8(0x48), 0x8b, 0x84, 0x24, 0x80, 0, 0, 0] {
2007 has_stack_c_array_arg_load = true
2008 }
2009 if image[off..off + 5] == [u8(0xba), 0x08, 0, 0, 0] {
2010 has_heap_zero_memory_flag = true
2011 }
2012 if image[off..off + 4] == [u8(0x49), 0x83, 0xc0, 0x20] {
2013 has_managed_array_allocation_padding_size = true
2014 }
2015 if image[off..off + 4] == [u8(0x49), 0x83, 0xc3, 0x17] {
2016 has_header_align_rounding = true
2017 }
2018 if image[off..off + 4] == [u8(0x49), 0x83, 0xe3, 0xf0] {
2019 has_header_align_mask = true
2020 }
2021 if image[off..off + 20] == [
2022 u8(0x49),
2023 0x89,
2024 0x43,
2025 0xf8,
2026 0x41,
2027 0xc6,
2028 0x03,
2029 0x00,
2030 0x48,
2031 0x8b,
2032 0x4c,
2033 0x24,
2034 0x20,
2035 0x49,
2036 0x8d,
2037 0x43,
2038 0x08,
2039 0x48,
2040 0x89,
2041 0x01,
2042 ] {
2043 has_cookie_before_array_header = true
2044 has_array_header_false_store = true
2045 has_data_after_array_header_store = true
2046 }
2047 if image[off..off + 3] == [u8(0x49), 0x89, 0x03] {
2048 has_cookie_over_array_header = true
2049 }
2050 if image[off..off + 4] == [u8(0x4d), 0x8d, 0x43, 0x08] {
2051 has_copy_to_data_after_array_header = true
2052 }
2053 if image[off..off + 4] == [u8(0x49), 0x83, 0xc3, 0x08] {
2054 has_extra_header_skip_after_cookie = true
2055 }
2056 if image[off..off + 4] == [u8(0x4c), 0x8d, 0x58, 0x08] {
2057 has_naive_heap_plus_header_data = true
2058 }
2059 if image[off..off + 7] == [u8(0xc7), 0x41, 0x14, 0x30, 0, 0, 0] {
2060 has_noscan_flags_store = true
2061 }
2062 if image[off..off + 3] == [u8(0x89), 0x41, 0x18] {
2063 has_element_size_store = true
2064 }
2065 if image[off..off + 6] == [u8(0x41), 0x8a, 0x12, 0x41, 0x88, 0x10] {
2066 has_byte_copy_loop = true
2067 }
2068 }
2069 assert has_stack_c_array_arg_load
2070 assert has_heap_zero_memory_flag
2071 assert has_managed_array_allocation_padding_size
2072 assert has_header_align_rounding
2073 assert has_header_align_mask
2074 assert has_cookie_before_array_header
2075 assert has_array_header_false_store
2076 assert has_data_after_array_header_store
2077 assert has_copy_to_data_after_array_header
2078 assert !has_cookie_over_array_header
2079 assert !has_extra_header_skip_after_cookie
2080 assert !has_naive_heap_plus_header_data
2081 assert has_noscan_flags_store
2082 assert has_element_size_store
2083 assert has_byte_copy_loop
2084}
2085
2086fn test_pe_linker_resolves_new_array_noscan_with_internal_heap_thunk() {
2087 mut obj := CoffObject.new()
2088 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
2089 obj.add_symbol('main', 5, true, 1)
2090 new_array_sym := obj.add_undefined('builtin____new_array_noscan')
2091 obj.add_text_reloc(1, new_array_sym, coff_image_rel_amd64_rel32)
2092
2093 mut linker := PeLinker.new(obj)
2094 image := linker.image() or { panic(err) }
2095 text_off := pe_test_section_header(image, '.text')
2096 text_rva := pe_test_u32(image, text_off + 12)
2097 text_raw := int(pe_test_u32(image, text_off + 20))
2098 text_raw_size := int(pe_test_u32(image, text_off + 16))
2099 entry_stub_len := linker.entry_stub_size()
2100 runtime_start_rva := text_rva + u32(entry_stub_len + obj.text_data.len)
2101
2102 mut first_import_thunk_file_off := -1
2103 for off in text_raw + entry_stub_len + obj.text_data.len .. text_raw + text_raw_size - 1 {
2104 if image[off] == 0xff && image[off + 1] == 0x25 {
2105 first_import_thunk_file_off = off
2106 break
2107 }
2108 }
2109 assert first_import_thunk_file_off > 0
2110 first_import_thunk_rva := text_rva + u32(first_import_thunk_file_off - text_raw)
2111
2112 new_array_field_off := text_raw + entry_stub_len + 1
2113 new_array_field_rva := text_rva + u32(entry_stub_len + 1)
2114 new_array_target := u32(i32(new_array_field_rva + 4) + pe_test_i32(image, new_array_field_off))
2115 assert new_array_target >= runtime_start_rva
2116 assert new_array_target < first_import_thunk_rva
2117
2118 new_array_off := pe_test_rva_to_file_off(image, new_array_target)
2119 assert new_array_off > 0
2120 assert image[new_array_off..new_array_off + 4] == [u8(0x48), 0x83, 0xec, 0x58] // sub rsp, 88
2121 names := pe_test_import_names(image)
2122 assert 'GetProcessHeap' in names
2123 assert 'HeapAlloc' in names
2124}
2125
2126fn test_pe_linker_rejects_unsupported_external_symbols() {
2127 mut obj := CoffObject.new()
2128 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
2129 obj.add_symbol('main', 0, true, 1)
2130 unknown_sym := obj.add_undefined('v_missing_runtime_symbol')
2131 obj.add_text_reloc(1, unknown_sym, coff_image_rel_amd64_rel32)
2132
2133 mut linker := PeLinker.new(obj)
2134 if _ := linker.image() {
2135 assert false
2136 } else {
2137 assert err.msg().starts_with('x64: unsupported backend feature: ')
2138 assert err.msg().contains('cannot resolve external symbol `v_missing_runtime_symbol` yet')
2139 assert err.msg().contains('.text relocation offset 0x00000001')
2140 assert err.msg().contains('near `main`+0x00000001')
2141 }
2142}
2143
2144fn test_pe_linker_rejects_crt_stdio_symbols_with_minimal_runtime_message() {
2145 mut obj := CoffObject.new()
2146 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
2147 obj.add_symbol('main', 0, true, 1)
2148 stderr_sym := obj.add_undefined('stderr')
2149 obj.add_text_reloc(1, stderr_sym, coff_image_rel_amd64_rel32)
2150
2151 mut linker := PeLinker.new(obj)
2152 if _ := linker.image() {
2153 assert false
2154 } else {
2155 assert err.msg().starts_with('x64: unsupported backend feature: ')
2156 assert err.msg().contains('cannot resolve C stdio/file-descriptor symbol `stderr`')
2157 assert err.msg().contains('Windows x64 native backend uses Kernel32 handles')
2158 assert err.msg().contains('Kernel32 handles')
2159 assert err.msg().contains('C FILE/stdio calls')
2160 assert !err.msg().contains('reachable helper')
2161 assert !err.msg().contains('stdout/stderr path')
2162 assert !err.msg().contains('C FILE streams')
2163 assert err.msg().contains('near `main`+0x00000001')
2164 }
2165}
2166
2167fn test_pe_linker_rejects_common_crt_stdio_functions_with_minimal_runtime_message() {
2168 for symbol_name in ['setvbuf', 'printf', 'snprintf', 'puts', 'fputs', 'fopen'] {
2169 mut obj := CoffObject.new()
2170 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
2171 obj.add_symbol('main', 0, true, 1)
2172 stdio_sym := obj.add_undefined(symbol_name)
2173 obj.add_text_reloc(1, stdio_sym, coff_image_rel_amd64_rel32)
2174
2175 mut linker := PeLinker.new(obj)
2176 if _ := linker.image() {
2177 assert false
2178 } else {
2179 assert err.msg().starts_with('x64: unsupported backend feature: ')
2180 assert err.msg().contains('cannot resolve C stdio/file-descriptor symbol `${symbol_name}`')
2181 assert err.msg().contains('Windows x64 native backend uses Kernel32 handles')
2182 assert err.msg().contains('Kernel32 handles')
2183 assert err.msg().contains('C FILE/stdio calls')
2184 assert !err.msg().contains('reachable helper')
2185 assert !err.msg().contains('stdout/stderr path')
2186 assert !err.msg().contains('C FILE streams')
2187 assert err.msg().contains('near `main`+0x00000001')
2188 }
2189 }
2190}
2191
2192fn test_pe_linker_rejects_missing_v_runtime_helper_with_targeted_message() {
2193 mut obj := CoffObject.new()
2194 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
2195 obj.add_symbol('main', 0, true, 1)
2196 helper_sym := obj.add_undefined('builtin__Map_string_int__keys')
2197 obj.add_text_reloc(1, helper_sym, coff_image_rel_amd64_rel32)
2198
2199 mut linker := PeLinker.new(obj)
2200 if _ := linker.image() {
2201 assert false
2202 } else {
2203 assert err.msg().starts_with('x64: unsupported backend feature: ')
2204 assert err.msg().contains('cannot resolve V runtime helper `builtin__Map_string_int__keys`')
2205 assert err.msg().contains('native x64 backend does not implement this feature for this target yet')
2206 assert !err.msg().contains('not imported')
2207 assert !err.msg().contains('must be compiled')
2208 assert !err.msg().contains('lowered before linking')
2209 assert err.msg().contains('near `main`+0x00000001')
2210 }
2211}
2212
2213fn test_pe_linker_rejects_relocation_to_omitted_rdata_section() {
2214 mut obj := CoffObject.new()
2215 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
2216 obj.add_symbol('main', 0, true, 1)
2217 rodata_sym := obj.add_symbol('missing_rodata', 0, false, 2)
2218 obj.add_text_reloc(1, rodata_sym, coff_image_rel_amd64_rel32)
2219
2220 mut linker := PeLinker.new(obj)
2221 if _ := linker.image() {
2222 assert false
2223 } else {
2224 assert err.msg().contains('relocation references .rdata')
2225 assert err.msg().contains('no .rdata section was emitted')
2226 }
2227}
2228
2229fn test_pe_linker_rejects_relocation_to_omitted_data_section() {
2230 mut obj := CoffObject.new()
2231 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
2232 obj.add_symbol('main', 0, true, 1)
2233 data_sym := obj.add_symbol('missing_data', 0, false, 3)
2234 obj.add_text_reloc(1, data_sym, coff_image_rel_amd64_rel32)
2235
2236 mut linker := PeLinker.new(obj)
2237 if _ := linker.image() {
2238 assert false
2239 } else {
2240 assert err.msg().contains('relocation references .data')
2241 assert err.msg().contains('no .data section was emitted')
2242 }
2243}
2244
2245fn test_x64_gen_link_executable_writes_pe_image() {
2246 path := os.join_path(os.temp_dir(), 'v_x64_pe_link_api_test_${rand.ulid()}.exe')
2247 os.rm(path) or {}
2248 defer {
2249 os.rm(path) or {}
2250 }
2251
2252 mut mod := mir.Module{}
2253 mut gen := Gen.new_with_format_and_abi(&mod, .coff, .windows)
2254 gen.coff.text_data << u8(0xc3)
2255 gen.coff.add_symbol('main', 0, true, 1)
2256
2257 gen.link_executable(path) or { panic(err) }
2258 assert os.is_file(path)
2259 assert os.file_size(path) > 0
2260 image := os.read_bytes(path) or { panic(err) }
2261
2262 assert image[0] == `M`
2263 assert image[1] == `Z`
2264 pe_off := int(pe_test_u32(image, 0x3c))
2265 assert pe_test_u32(image, pe_off) == pe_signature
2266 assert pe_test_u16(image, pe_off + 4) == coff_image_file_machine_amd64
2267}
2268
2269fn test_pe_linker_write_postcondition_rejects_missing_or_empty_output() {
2270 missing_path := os.join_path(os.temp_dir(),
2271 'v_x64_pe_link_missing_postcondition_test_${rand.ulid()}.exe')
2272 empty_path := os.join_path(os.temp_dir(),
2273 'v_x64_pe_link_empty_postcondition_test_${rand.ulid()}.exe')
2274 os.rm(missing_path) or {}
2275 os.rm(empty_path) or {}
2276 defer {
2277 os.rm(missing_path) or {}
2278 os.rm(empty_path) or {}
2279 }
2280
2281 if _ := pe_check_written_image(missing_path, 512) {
2282 assert false
2283 } else {
2284 assert err.msg().contains('was not created as a file')
2285 }
2286
2287 os.write_file(empty_path, '') or { panic(err) }
2288 if _ := pe_check_written_image(empty_path, 512) {
2289 assert false
2290 } else {
2291 assert err.msg().contains('has size 0 bytes')
2292 }
2293}
2294
2295fn test_x64_gen_link_executable_requires_windows_abi() {
2296 path := os.join_path(os.temp_dir(), 'v_x64_pe_link_api_sysv_test.exe')
2297 defer {
2298 os.rm(path) or {}
2299 }
2300
2301 mut mod := mir.Module{}
2302 mut gen := Gen.new_with_format(&mod, .coff)
2303 gen.coff.text_data << u8(0xc3)
2304 gen.coff.add_symbol('main', 0, true, 1)
2305
2306 if _ := gen.link_executable(path) {
2307 assert false
2308 } else {
2309 assert err.msg().contains('requires Windows ABI')
2310 }
2311}
2312
2313fn test_x64_gen_link_executable_rejects_non_coff_format() {
2314 path := os.join_path(os.temp_dir(), 'v_x64_pe_link_api_elf_test.exe')
2315 defer {
2316 os.rm(path) or {}
2317 }
2318
2319 mut mod := mir.Module{}
2320 mut gen := Gen.new_with_format_and_abi(&mod, .elf, .sysv)
2321
2322 if _ := gen.link_executable(path) {
2323 assert false
2324 } else {
2325 assert err.msg().contains('only implemented for COFF')
2326 }
2327}
2328