1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 package amd64
32
33 import (
34 "cmd/internal/objabi"
35 "cmd/internal/sys"
36 "cmd/link/internal/ld"
37 "cmd/link/internal/loader"
38 "cmd/link/internal/sym"
39 "debug/elf"
40 "log"
41 )
42
43 func PADDR(x uint32) uint32 {
44 return x &^ 0x80000000
45 }
46
47 func gentext(ctxt *ld.Link, ldr *loader.Loader) {
48 initfunc, addmoduledata := ld.PrepareAddmoduledata(ctxt)
49 if initfunc == nil {
50 return
51 }
52
53 o := func(op ...uint8) {
54 for _, op1 := range op {
55 initfunc.AddUint8(op1)
56 }
57 }
58
59
60
61
62 o(0x48, 0x8d, 0x3d)
63 initfunc.AddPCRelPlus(ctxt.Arch, ctxt.Moduledata, 0)
64
65
66 o(0xe8)
67 initfunc.AddSymRef(ctxt.Arch, addmoduledata, 0, objabi.R_CALL, 4)
68
69 o(0xc3)
70 }
71
72 func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loader.Sym, r loader.Reloc, rIdx int) bool {
73 targ := r.Sym()
74 var targType sym.SymKind
75 if targ != 0 {
76 targType = ldr.SymType(targ)
77 }
78
79 switch rt := r.Type(); rt {
80 default:
81 if rt >= objabi.ElfRelocOffset {
82 ldr.Errorf(s, "unexpected relocation type %d (%s)", r.Type(), sym.RelocName(target.Arch, r.Type()))
83 return false
84 }
85
86
87 case objabi.ElfRelocOffset + objabi.RelocType(elf.R_X86_64_PC32):
88 if targType == sym.SDYNIMPORT {
89 ldr.Errorf(s, "unexpected R_X86_64_PC32 relocation for dynamic symbol %s", ldr.SymName(targ))
90 }
91 if targType == 0 || targType == sym.SXREF {
92 ldr.Errorf(s, "unknown symbol %s in pcrel", ldr.SymName(targ))
93 }
94 su := ldr.MakeSymbolUpdater(s)
95 su.SetRelocType(rIdx, objabi.R_PCREL)
96 su.SetRelocAdd(rIdx, r.Add()+4)
97 return true
98
99 case objabi.ElfRelocOffset + objabi.RelocType(elf.R_X86_64_PC64):
100 if targType == sym.SDYNIMPORT {
101 ldr.Errorf(s, "unexpected R_X86_64_PC64 relocation for dynamic symbol %s", ldr.SymName(targ))
102 }
103 if targType == 0 || targType == sym.SXREF {
104 ldr.Errorf(s, "unknown symbol %s in pcrel", ldr.SymName(targ))
105 }
106 su := ldr.MakeSymbolUpdater(s)
107 su.SetRelocType(rIdx, objabi.R_PCREL)
108 su.SetRelocAdd(rIdx, r.Add()+8)
109 return true
110
111 case objabi.ElfRelocOffset + objabi.RelocType(elf.R_X86_64_PLT32):
112 su := ldr.MakeSymbolUpdater(s)
113 su.SetRelocType(rIdx, objabi.R_PCREL)
114 su.SetRelocAdd(rIdx, r.Add()+4)
115 if targType == sym.SDYNIMPORT {
116 addpltsym(target, ldr, syms, targ)
117 su.SetRelocSym(rIdx, syms.PLT)
118 su.SetRelocAdd(rIdx, r.Add()+int64(ldr.SymPlt(targ)))
119 }
120
121 return true
122
123 case objabi.ElfRelocOffset + objabi.RelocType(elf.R_X86_64_GOTPCREL),
124 objabi.ElfRelocOffset + objabi.RelocType(elf.R_X86_64_GOTPCRELX),
125 objabi.ElfRelocOffset + objabi.RelocType(elf.R_X86_64_REX_GOTPCRELX):
126 su := ldr.MakeSymbolUpdater(s)
127 if targType != sym.SDYNIMPORT {
128
129 sData := ldr.Data(s)
130 if r.Off() >= 2 && sData[r.Off()-2] == 0x8b {
131 su.MakeWritable()
132
133 writeableData := su.Data()
134 writeableData[r.Off()-2] = 0x8d
135 su.SetRelocType(rIdx, objabi.R_PCREL)
136 su.SetRelocAdd(rIdx, r.Add()+4)
137 return true
138 }
139 }
140
141
142
143 ld.AddGotSym(target, ldr, syms, targ, uint32(elf.R_X86_64_GLOB_DAT))
144
145 su.SetRelocType(rIdx, objabi.R_PCREL)
146 su.SetRelocSym(rIdx, syms.GOT)
147 su.SetRelocAdd(rIdx, r.Add()+4+int64(ldr.SymGot(targ)))
148 return true
149
150 case objabi.ElfRelocOffset + objabi.RelocType(elf.R_X86_64_64):
151 if targType == sym.SDYNIMPORT {
152 ldr.Errorf(s, "unexpected R_X86_64_64 relocation for dynamic symbol %s", ldr.SymName(targ))
153 }
154 su := ldr.MakeSymbolUpdater(s)
155 su.SetRelocType(rIdx, objabi.R_ADDR)
156 if target.IsPIE() && target.IsInternal() {
157
158
159
160 break
161 }
162 return true
163
164
165 case objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_UNSIGNED*2 + 0,
166 objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_SIGNED*2 + 0,
167 objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_BRANCH*2 + 0:
168 su := ldr.MakeSymbolUpdater(s)
169 su.SetRelocType(rIdx, objabi.R_ADDR)
170
171 if targType == sym.SDYNIMPORT {
172 ldr.Errorf(s, "unexpected reloc for dynamic symbol %s", ldr.SymName(targ))
173 }
174 if target.IsPIE() && target.IsInternal() {
175
176
177
178 if rt == objabi.MachoRelocOffset+ld.MACHO_X86_64_RELOC_UNSIGNED*2 {
179 break
180 } else {
181
182
183 ldr.Errorf(s, "unsupported relocation for PIE: %v", rt)
184 }
185 }
186 return true
187
188 case objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_SUBTRACTOR*2 + 0:
189
190
191
192
193
194
195
196
197
198
199
200
201
202 su := ldr.MakeSymbolUpdater(s)
203 outer, off := ld.FoldSubSymbolOffset(ldr, targ)
204 if outer != s {
205 ldr.Errorf(s, "unsupported X86_64_RELOC_SUBTRACTOR reloc: target %s, outer %s",
206 ldr.SymName(targ), ldr.SymName(outer))
207 break
208 }
209 relocs := su.Relocs()
210 if rIdx+1 >= relocs.Count() || relocs.At(rIdx+1).Type() != objabi.MachoRelocOffset+ld.MACHO_X86_64_RELOC_UNSIGNED*2+0 || relocs.At(rIdx+1).Off() != r.Off() {
211 ldr.Errorf(s, "unexpected X86_64_RELOC_SUBTRACTOR reloc, must be followed by X86_64_RELOC_UNSIGNED at the same offset: %d %v %d", rIdx, relocs.At(rIdx+1).Type(), relocs.At(rIdx+1).Off())
212 }
213
214 su.SetRelocType(rIdx+1, objabi.R_PCREL)
215 su.SetRelocAdd(rIdx+1, r.Add()+int64(r.Off())-off)
216
217 su.SetRelocSiz(rIdx, 0)
218 return true
219
220 case objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_BRANCH*2 + 1:
221 if targType == sym.SDYNIMPORT {
222 addpltsym(target, ldr, syms, targ)
223 su := ldr.MakeSymbolUpdater(s)
224 su.SetRelocSym(rIdx, syms.PLT)
225 su.SetRelocType(rIdx, objabi.R_PCREL)
226 su.SetRelocAdd(rIdx, int64(ldr.SymPlt(targ)))
227 return true
228 }
229 fallthrough
230
231 case objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_UNSIGNED*2 + 1,
232 objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_SIGNED*2 + 1,
233 objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_SIGNED_1*2 + 1,
234 objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_SIGNED_2*2 + 1,
235 objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_SIGNED_4*2 + 1:
236 su := ldr.MakeSymbolUpdater(s)
237 su.SetRelocType(rIdx, objabi.R_PCREL)
238
239 if targType == sym.SDYNIMPORT {
240 ldr.Errorf(s, "unexpected pc-relative reloc for dynamic symbol %s", ldr.SymName(targ))
241 }
242 return true
243
244 case objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_GOT_LOAD*2 + 1:
245 if targType != sym.SDYNIMPORT {
246
247
248 sdata := ldr.Data(s)
249 if r.Off() < 2 || sdata[r.Off()-2] != 0x8b {
250 ldr.Errorf(s, "unexpected GOT_LOAD reloc for non-dynamic symbol %s", ldr.SymName(targ))
251 return false
252 }
253
254 su := ldr.MakeSymbolUpdater(s)
255 su.MakeWritable()
256 sdata = su.Data()
257 sdata[r.Off()-2] = 0x8d
258 su.SetRelocType(rIdx, objabi.R_PCREL)
259 return true
260 }
261 fallthrough
262
263 case objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_GOT*2 + 1:
264 if targType != sym.SDYNIMPORT {
265 ldr.Errorf(s, "unexpected GOT reloc for non-dynamic symbol %s", ldr.SymName(targ))
266 }
267 ld.AddGotSym(target, ldr, syms, targ, 0)
268 su := ldr.MakeSymbolUpdater(s)
269 su.SetRelocType(rIdx, objabi.R_PCREL)
270 su.SetRelocSym(rIdx, syms.GOT)
271 su.SetRelocAdd(rIdx, r.Add()+int64(ldr.SymGot(targ)))
272 return true
273 }
274
275
276 relocs := ldr.Relocs(s)
277 r = relocs.At(rIdx)
278
279 switch r.Type() {
280 case objabi.R_CALL:
281 if targType != sym.SDYNIMPORT {
282
283 return true
284 }
285 if target.IsExternal() {
286
287 return true
288 }
289
290
291 addpltsym(target, ldr, syms, targ)
292 su := ldr.MakeSymbolUpdater(s)
293 su.SetRelocSym(rIdx, syms.PLT)
294 su.SetRelocAdd(rIdx, int64(ldr.SymPlt(targ)))
295 return true
296
297 case objabi.R_PCREL:
298 if targType == sym.SDYNIMPORT && ldr.SymType(s).IsText() && target.IsDarwin() {
299
300
301 if r.Add() != 0 {
302 ldr.Errorf(s, "unexpected nonzero addend for dynamic symbol %s", ldr.SymName(targ))
303 return false
304 }
305 su := ldr.MakeSymbolUpdater(s)
306 if r.Off() >= 2 && su.Data()[r.Off()-2] == 0x8d {
307 su.MakeWritable()
308 su.Data()[r.Off()-2] = 0x8b
309 if target.IsInternal() {
310 ld.AddGotSym(target, ldr, syms, targ, 0)
311 su.SetRelocSym(rIdx, syms.GOT)
312 su.SetRelocAdd(rIdx, int64(ldr.SymGot(targ)))
313 } else {
314 su.SetRelocType(rIdx, objabi.R_GOTPCREL)
315 }
316 return true
317 }
318 ldr.Errorf(s, "unexpected R_PCREL reloc for dynamic symbol %s: not preceded by LEAQ instruction", ldr.SymName(targ))
319 }
320
321 case objabi.R_ADDR:
322 if ldr.SymType(s).IsText() && target.IsElf() {
323 su := ldr.MakeSymbolUpdater(s)
324 if target.IsSolaris() {
325 addpltsym(target, ldr, syms, targ)
326 su.SetRelocSym(rIdx, syms.PLT)
327 su.SetRelocAdd(rIdx, r.Add()+int64(ldr.SymPlt(targ)))
328 return true
329 }
330
331
332
333 ld.AddGotSym(target, ldr, syms, targ, uint32(elf.R_X86_64_GLOB_DAT))
334
335 su.SetRelocSym(rIdx, syms.GOT)
336 su.SetRelocAdd(rIdx, r.Add()+int64(ldr.SymGot(targ)))
337 return true
338 }
339
340
341 if target.IsPIE() && target.IsInternal() {
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373 switch ldr.SymName(s) {
374 case ".dynsym", ".rela", ".rela.plt", ".got.plt", ".dynamic":
375 return false
376 }
377 } else {
378
379
380
381
382
383
384 if t := ldr.SymType(s); !t.IsDATA() && !t.IsRODATA() {
385 break
386 }
387 }
388
389 if target.IsElf() {
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407 rela := ldr.MakeSymbolUpdater(syms.Rela)
408 rela.AddAddrPlus(target.Arch, s, int64(r.Off()))
409 if r.Siz() == 8 {
410 rela.AddUint64(target.Arch, elf.R_INFO(0, uint32(elf.R_X86_64_RELATIVE)))
411 } else {
412 ldr.Errorf(s, "unexpected relocation for dynamic symbol %s", ldr.SymName(targ))
413 }
414 rela.AddAddrPlus(target.Arch, targ, int64(r.Add()))
415
416
417
418
419 return true
420 }
421
422 if target.IsDarwin() {
423
424
425
426 ld.MachoAddRebase(s, int64(r.Off()))
427
428
429
430
431 return true
432 }
433 case objabi.R_GOTPCREL:
434 if target.IsExternal() {
435
436 return true
437 }
438
439
440 }
441
442 return false
443 }
444
445 func elfreloc1(ctxt *ld.Link, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym, r loader.ExtReloc, ri int, sectoff int64) bool {
446 out.Write64(uint64(sectoff))
447
448 elfsym := ld.ElfSymForReloc(ctxt, r.Xsym)
449 siz := r.Size
450 switch r.Type {
451 default:
452 return false
453 case objabi.R_ADDR, objabi.R_DWARFSECREF:
454 if siz == 4 {
455 out.Write64(uint64(elf.R_X86_64_32) | uint64(elfsym)<<32)
456 } else if siz == 8 {
457 out.Write64(uint64(elf.R_X86_64_64) | uint64(elfsym)<<32)
458 } else {
459 return false
460 }
461 case objabi.R_TLS_LE:
462 if siz == 4 {
463 out.Write64(uint64(elf.R_X86_64_TPOFF32) | uint64(elfsym)<<32)
464 } else {
465 return false
466 }
467 case objabi.R_TLS_IE:
468 if siz == 4 {
469 out.Write64(uint64(elf.R_X86_64_GOTTPOFF) | uint64(elfsym)<<32)
470 } else {
471 return false
472 }
473 case objabi.R_CALL:
474 if siz == 4 {
475 if ldr.SymType(r.Xsym) == sym.SDYNIMPORT {
476 out.Write64(uint64(elf.R_X86_64_PLT32) | uint64(elfsym)<<32)
477 } else {
478 out.Write64(uint64(elf.R_X86_64_PC32) | uint64(elfsym)<<32)
479 }
480 } else {
481 return false
482 }
483 case objabi.R_PCREL:
484 if siz == 4 {
485 if ldr.SymType(r.Xsym) == sym.SDYNIMPORT && ldr.SymElfType(r.Xsym) == elf.STT_FUNC {
486 out.Write64(uint64(elf.R_X86_64_PLT32) | uint64(elfsym)<<32)
487 } else {
488 out.Write64(uint64(elf.R_X86_64_PC32) | uint64(elfsym)<<32)
489 }
490 } else {
491 return false
492 }
493 case objabi.R_GOTPCREL:
494 if siz == 4 {
495 out.Write64(uint64(elf.R_X86_64_GOTPCREL) | uint64(elfsym)<<32)
496 } else {
497 return false
498 }
499 }
500
501 out.Write64(uint64(r.Xadd))
502 return true
503 }
504
505 func machoreloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym, r loader.ExtReloc, sectoff int64) bool {
506 var v uint32
507
508 rs := r.Xsym
509 rt := r.Type
510
511 if !ldr.SymType(s).IsDWARF() {
512 if ldr.SymDynid(rs) < 0 {
513 ldr.Errorf(s, "reloc %d (%s) to non-macho symbol %s type=%d (%s)", rt, sym.RelocName(arch, rt), ldr.SymName(rs), ldr.SymType(rs), ldr.SymType(rs))
514 return false
515 }
516
517 v = uint32(ldr.SymDynid(rs))
518 v |= 1 << 27
519 } else {
520 v = uint32(ldr.SymSect(rs).Extnum)
521 if v == 0 {
522 ldr.Errorf(s, "reloc %d (%s) to symbol %s in non-macho section %s type=%d (%s)", rt, sym.RelocName(arch, rt), ldr.SymName(rs), ldr.SymSect(rs).Name, ldr.SymType(rs), ldr.SymType(rs))
523 return false
524 }
525 }
526
527 switch rt {
528 default:
529 return false
530
531 case objabi.R_ADDR:
532 v |= ld.MACHO_X86_64_RELOC_UNSIGNED << 28
533
534 case objabi.R_CALL:
535 v |= 1 << 24
536 v |= ld.MACHO_X86_64_RELOC_BRANCH << 28
537
538
539 case objabi.R_PCREL:
540 v |= 1 << 24
541 v |= ld.MACHO_X86_64_RELOC_SIGNED << 28
542 case objabi.R_GOTPCREL:
543 v |= 1 << 24
544 v |= ld.MACHO_X86_64_RELOC_GOT_LOAD << 28
545 }
546
547 switch r.Size {
548 default:
549 return false
550
551 case 1:
552 v |= 0 << 25
553
554 case 2:
555 v |= 1 << 25
556
557 case 4:
558 v |= 2 << 25
559
560 case 8:
561 v |= 3 << 25
562 }
563
564 out.Write32(uint32(sectoff))
565 out.Write32(v)
566 return true
567 }
568
569 func pereloc1(arch *sys.Arch, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym, r loader.ExtReloc, sectoff int64) bool {
570 var v uint32
571
572 rs := r.Xsym
573 rt := r.Type
574
575 if ldr.SymDynid(rs) < 0 {
576 ldr.Errorf(s, "reloc %d (%s) to non-coff symbol %s type=%d (%s)", rt, sym.RelocName(arch, rt), ldr.SymName(rs), ldr.SymType(rs), ldr.SymType(rs))
577 return false
578 }
579
580 out.Write32(uint32(sectoff))
581 out.Write32(uint32(ldr.SymDynid(rs)))
582
583 switch rt {
584 default:
585 return false
586
587 case objabi.R_DWARFSECREF:
588 v = ld.IMAGE_REL_AMD64_SECREL
589
590 case objabi.R_ADDR:
591 if r.Size == 8 {
592 v = ld.IMAGE_REL_AMD64_ADDR64
593 } else {
594 v = ld.IMAGE_REL_AMD64_ADDR32
595 }
596
597 case objabi.R_PEIMAGEOFF:
598 v = ld.IMAGE_REL_AMD64_ADDR32NB
599
600 case objabi.R_CALL,
601 objabi.R_PCREL:
602 v = ld.IMAGE_REL_AMD64_REL32
603 }
604
605 out.Write16(uint16(v))
606
607 return true
608 }
609
610 func archreloc(*ld.Target, *loader.Loader, *ld.ArchSyms, loader.Reloc, loader.Sym, int64) (int64, int, bool) {
611 return -1, 0, false
612 }
613
614 func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant, loader.Sym, int64, []byte) int64 {
615 log.Fatalf("unexpected relocation variant")
616 return -1
617 }
618
619 func elfsetupplt(ctxt *ld.Link, ldr *loader.Loader, plt, got *loader.SymbolBuilder, dynamic loader.Sym) {
620 if plt.Size() == 0 {
621
622 plt.AddUint8(0xff)
623
624 plt.AddUint8(0x35)
625 plt.AddPCRelPlus(ctxt.Arch, got.Sym(), 8)
626
627
628 plt.AddUint8(0xff)
629
630 plt.AddUint8(0x25)
631 plt.AddPCRelPlus(ctxt.Arch, got.Sym(), 16)
632
633
634 plt.AddUint32(ctxt.Arch, 0x00401f0f)
635
636
637 got.AddAddrPlus(ctxt.Arch, dynamic, 0)
638
639 got.AddUint64(ctxt.Arch, 0)
640 got.AddUint64(ctxt.Arch, 0)
641 }
642 }
643
644 func addpltsym(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loader.Sym) {
645 if ldr.SymPlt(s) >= 0 {
646 return
647 }
648
649 ld.Adddynsym(ldr, target, syms, s)
650
651 if target.IsElf() {
652 plt := ldr.MakeSymbolUpdater(syms.PLT)
653 got := ldr.MakeSymbolUpdater(syms.GOTPLT)
654 rela := ldr.MakeSymbolUpdater(syms.RelaPLT)
655 if plt.Size() == 0 {
656 panic("plt is not set up")
657 }
658
659
660 plt.AddUint8(0xff)
661
662 plt.AddUint8(0x25)
663 plt.AddPCRelPlus(target.Arch, got.Sym(), got.Size())
664
665
666 got.AddAddrPlus(target.Arch, plt.Sym(), plt.Size())
667
668
669 plt.AddUint8(0x68)
670
671 plt.AddUint32(target.Arch, uint32((got.Size()-24-8)/8))
672
673
674 plt.AddUint8(0xe9)
675
676 plt.AddUint32(target.Arch, uint32(-(plt.Size() + 4)))
677
678
679 rela.AddAddrPlus(target.Arch, got.Sym(), got.Size()-8)
680
681 sDynid := ldr.SymDynid(s)
682 rela.AddUint64(target.Arch, elf.R_INFO(uint32(sDynid), uint32(elf.R_X86_64_JMP_SLOT)))
683 rela.AddUint64(target.Arch, 0)
684
685 ldr.SetPlt(s, int32(plt.Size()-16))
686 } else if target.IsDarwin() {
687 ld.AddGotSym(target, ldr, syms, s, 0)
688
689 sDynid := ldr.SymDynid(s)
690 lep := ldr.MakeSymbolUpdater(syms.LinkEditPLT)
691 lep.AddUint32(target.Arch, uint32(sDynid))
692
693 plt := ldr.MakeSymbolUpdater(syms.PLT)
694 ldr.SetPlt(s, int32(plt.Size()))
695
696
697 plt.AddUint8(0xff)
698 plt.AddUint8(0x25)
699 plt.AddPCRelPlus(target.Arch, syms.GOT, int64(ldr.SymGot(s)))
700 } else {
701 ldr.Errorf(s, "addpltsym: unsupported binary format")
702 }
703 }
704
705 func tlsIEtoLE(P []byte, off, size int) {
706
707
708
709
710
711
712
713 if off < 3 {
714 log.Fatal("R_X86_64_GOTTPOFF reloc not preceded by MOVQ or ADDQ instruction")
715 }
716 op := P[off-3 : off]
717 reg := op[2] >> 3
718
719 if op[1] == 0x8b || reg == 4 {
720
721 if op[0] == 0x4c {
722 op[0] = 0x49
723 } else if size == 4 && op[0] == 0x44 {
724 op[0] = 0x41
725 }
726 if op[1] == 0x8b {
727 op[1] = 0xc7
728 } else {
729 op[1] = 0x81
730 }
731 op[2] = 0xc0 | reg
732 } else {
733
734
735
736
737 log.Fatalf("expected TLS IE op to be MOVQ, got %v", op)
738 }
739 }
740
View as plain text