ตัวอย่างการย้ายที่อยู่ขั้นต่ำ
การย้ายที่อยู่เป็นหน้าที่สำคัญอย่างหนึ่งของการเชื่อมโยง
ลองมาดูวิธีการทำงานกับตัวอย่างขั้นต่ำ
0) บทนำ
สรุป: การย้ายตำแหน่งแก้ไข.text
ส่วนของไฟล์ออบเจ็กต์ที่จะแปล:
- ที่อยู่ไฟล์ออบเจ็กต์
- ลงในที่อยู่สุดท้ายของไฟล์ปฏิบัติการ
สิ่งนี้ต้องทำโดยตัวเชื่อมโยงเนื่องจากคอมไพเลอร์เห็นไฟล์อินพุตทีละไฟล์เท่านั้น แต่เราต้องรู้เกี่ยวกับไฟล์อ็อบเจ็กต์ทั้งหมดพร้อมกันเพื่อตัดสินใจว่าจะทำอย่างไร:
- แก้ไขสัญลักษณ์ที่ไม่ได้กำหนดเช่นฟังก์ชันที่ไม่ได้กำหนดที่ประกาศไว้
- ไม่ปะทะกันหลายส่วน
.text
และ.data
ส่วนของไฟล์อ็อบเจ็กต์หลายไฟล์
วิชาบังคับก่อน: มีความเข้าใจน้อยที่สุดเกี่ยวกับ:
การเชื่อมโยงไม่มีส่วนเกี่ยวข้องกับ C หรือ C ++ โดยเฉพาะ: คอมไพเลอร์สร้างไฟล์อ็อบเจ็กต์ จากนั้นตัวเชื่อมโยงจะนำข้อมูลเหล่านั้นไปเป็นอินพุตโดยไม่ทราบว่าภาษาใดรวบรวม มันอาจเป็น Fortran เช่นกัน
ดังนั้นเพื่อลดเปลือกลองศึกษา NASM x86-64 ELF Linux สวัสดีชาวโลก:
section .data
hello_world db "Hello world!", 10
section .text
global _start
_start:
; sys_write
mov rax, 1
mov rdi, 1
mov rsi, hello_world
mov rdx, 13
syscall
; sys_exit
mov rax, 60
mov rdi, 0
syscall
รวบรวมและประกอบด้วย:
nasm -o hello_world.o hello_world.asm
ld -o hello_world.out hello_world.o
ด้วย NASM 2.10.09
1). ข้อความของ. o
ก่อนอื่นเราแยก.text
ส่วนของไฟล์ออบเจ็กต์:
objdump -d hello_world.o
ซึ่งจะช่วยให้:
0000000000000000 <_start>:
0: b8 01 00 00 00 mov $0x1,%eax
5: bf 01 00 00 00 mov $0x1,%edi
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
14: ba 0d 00 00 00 mov $0xd,%edx
19: 0f 05 syscall
1b: b8 3c 00 00 00 mov $0x3c,%eax
20: bf 00 00 00 00 mov $0x0,%edi
25: 0f 05 syscall
บรรทัดสำคัญคือ:
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
ซึ่งควรย้ายที่อยู่ของสตริง hello world ไปไว้ในไฟล์ rsi
รีจิสเตอร์ซึ่งจะส่งผ่านไปยังการเรียกระบบการเขียน
แต่เดี๋ยวก่อน! คอมไพเลอร์จะรู้ได้อย่างไรว่าอยู่ที่ไหน"Hello world!"
จะสิ้นสุดลงในหน่วยความจำเมื่อโหลดโปรแกรม?
มันทำไม่ได้โดยเฉพาะหลังจากที่เราเชื่อมโยง.o
ไฟล์หลาย ๆ ไฟล์เข้าด้วยกัน.data
ส่วน
มีเพียงผู้เชื่อมโยงเท่านั้นที่สามารถทำได้เนื่องจากมีเพียงไฟล์วัตถุเหล่านั้นเท่านั้นที่จะมี
ดังนั้นคอมไพเลอร์เพียง:
- ใส่ค่าตัวยึดตำแหน่ง
0x0
บนเอาต์พุตที่คอมไพล์แล้ว
- ให้ข้อมูลเพิ่มเติมแก่ผู้เชื่อมโยงเกี่ยวกับวิธีแก้ไขโค้ดที่คอมไพล์แล้วด้วยที่อยู่ที่ดี
"ข้อมูลเพิ่มเติม" นี้มีอยู่ในไฟล์ .rela.text
ส่วนของออบเจ็กต์ไฟล์
2) .rela.text
.rela.text
ย่อมาจาก "การย้ายส่วน. text"
ใช้คำว่า relocation เนื่องจากตัวเชื่อมโยงจะต้องย้ายที่อยู่จากวัตถุไปยังไฟล์ปฏิบัติการ
เราสามารถแยกชิ้น.rela.text
ส่วนได้ด้วย:
readelf -r hello_world.o
ซึ่งประกอบด้วย;
Relocation section '.rela.text' at offset 0x340 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
00000000000c 000200000001 R_X86_64_64 0000000000000000 .data + 0
รูปแบบของส่วนนี้ได้รับการแก้ไขแล้วที่: http://www.sco.com/developers/gabi/2003-12-17/ch4.reloc.html
แต่ละรายการจะบอกผู้เชื่อมโยงเกี่ยวกับที่อยู่หนึ่งที่ต้องย้ายที่นี่เรามีเพียงที่อยู่เดียวสำหรับสตริง
ทำให้ง่ายขึ้นเล็กน้อยสำหรับบรรทัดนี้เรามีข้อมูลต่อไปนี้:
Offset = C
: ไบต์แรกของการ.text
เปลี่ยนแปลงนี้คืออะไร
หากเรามองย้อนกลับไปที่ข้อความที่ถอดรหัสแล้วข้อความนั้นอยู่ในขั้นวิกฤตmovabs $0x0,%rsi
และผู้ที่รู้การเข้ารหัสคำสั่ง x86-64 จะสังเกตว่าสิ่งนี้เข้ารหัสส่วนที่อยู่ 64 บิตของคำสั่ง
Name = .data
: ที่อยู่ชี้ไปที่.data
ส่วน
Type = R_X86_64_64
ซึ่งระบุสิ่งที่ต้องคำนวณเพื่อแปลที่อยู่
ฟิลด์นี้ขึ้นอยู่กับโปรเซสเซอร์จริงๆดังนั้นจึงมีการบันทึกไว้ในส่วนขยาย AMD64 System V ABIส่วน 4.4 "การย้ายตำแหน่ง"
เอกสารนั้นระบุว่าR_X86_64_64
:
3) .text ของ. out
ตอนนี้ให้ดูที่พื้นที่ข้อความของไฟล์ปฏิบัติการที่ld
สร้างขึ้นสำหรับเรา:
objdump -d hello_world.out
ให้:
00000000004000b0 <_start>:
4000b0: b8 01 00 00 00 mov $0x1,%eax
4000b5: bf 01 00 00 00 mov $0x1,%edi
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
4000c1: 00 00 00
4000c4: ba 0d 00 00 00 mov $0xd,%edx
4000c9: 0f 05 syscall
4000cb: b8 3c 00 00 00 mov $0x3c,%eax
4000d0: bf 00 00 00 00 mov $0x0,%edi
4000d5: 0f 05 syscall
ดังนั้นสิ่งเดียวที่เปลี่ยนไปจากไฟล์ออบเจ็กต์คือเส้นวิกฤต:
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
4000c1: 00 00 00
ซึ่งขณะนี้ชี้ไปที่ที่อยู่0x6000d8
( d8 00 60 00 00 00 00 00
ในน้อย endian) 0x0
แทน
นี่คือตำแหน่งที่เหมาะสมสำหรับไฟล์ hello_world
สตริงหรือไม่
ในการตัดสินใจเราต้องตรวจสอบส่วนหัวของโปรแกรมซึ่งบอก Linux ว่าจะโหลดแต่ละส่วนไปที่ใด
เราแยกชิ้นส่วนด้วย:
readelf -l hello_world.out
ซึ่งจะช่วยให้:
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000000d7 0x00000000000000d7 R E 200000
LOAD 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8
0x000000000000000d 0x000000000000000d RW 200000
Section to Segment mapping:
Segment Sections...
00 .text
01 .data
นี้จะบอกเราว่า.data
ส่วนซึ่งเป็นคนที่สองเริ่มต้นที่=VirtAddr
0x06000d8
และสิ่งเดียวในส่วนข้อมูลคือสตริงสวัสดีชาวโลกของเรา
ระดับโบนัส