น่าเสียดายที่คำว่า ไลบรารีแบบคงที่ และ ไลบรารีแบบไดนามิก เป็นทั้งสองรูปแบบ ไลบรารี ADJECTIVE เพราะมันทำให้โปรแกรมเมอร์คิดว่าพวกเขาแสดงถึงตัวแปรตลอดเวลา ของสิ่งเดียวกันโดยพื้นฐานแล้ว สิ่งนี้เกือบจะทำให้เข้าใจผิดพอๆ กับความคิดที่ว่าสนามแบดมินตันและศาลฎีกาโดยพื้นฐานแล้วเป็นสิ่งเดียวกัน ในความเป็นจริง มันทำให้เข้าใจผิดมากกว่ามาก เนื่องจากไม่มีใครทนทุกข์ทรมานจากการคิดว่าสนามแบดมินตันและศาลฎีกาโดยพื้นฐานแล้วคือสิ่งเดียวกัน
ใครช่วยอธิบายความแตกต่างระหว่างเนื้อหาของไฟล์สแตติกและไลบรารีที่ใช้ร่วมกันได้บ้าง
ลองใช้ตัวอย่าง หากต้องการตอบโต้กับสนามแบดมินตัน / หมอกศาลฎีกา ฉันจะใช้คำศัพท์ทางเทคนิคที่แม่นยำยิ่งขึ้น แทนที่จะเป็น ไลบรารีแบบคงที่ ฉันจะพูดว่า ar
เก็บถาวร และแทนที่จะเป็น ไลบรารีแบบไดนามิก ฉันจะพูดว่า วัตถุที่ใช้ร่วมกันแบบไดนามิก หรือเรียกสั้น ๆ ว่า DSO
ไฟล์เก็บถาวร ar
คืออะไร
ฉันจะสร้างไฟล์เก็บถาวร ar
โดยเริ่มจากสามไฟล์นี้:
foo.c
#include <stdio.h>
void foo(void)
{
puts("foo");
}
บาร์.ซี
#include <stdio.h>
void bar(void)
{
puts("bar");
}
limerick.txt
There once was a young lady named bright
Whose speed was much faster than light
She set out one day
In a relative way
And returned on the previous night.
ฉันจะรวบรวมซอร์ส C ทั้งสองนั้นเป็นไฟล์อ็อบเจ็กต์ Position Independent:
$ gcc -c -Wall -fPIC foo.c
$ gcc -c -Wall -fPIC bar.c
ไม่จำเป็นต้องมีไฟล์อ็อบเจ็กต์ที่กำหนดไว้สำหรับไฟล์เก็บถาวร ar
ที่จะคอมไพล์ด้วย -fPIC
ฉันแค่ต้องการให้สิ่งเหล่านี้รวบรวมแบบนั้น
จากนั้น ฉันจะสร้างไฟล์เก็บถาวร ar
ชื่อ libsundry.a
ที่มีไฟล์อ็อบเจ็กต์ foo.o
และ bar.o
บวก limerick.txt
:
$ ar rcs libsundry.a foo.o bar.o limerick.txt
แน่นอนว่าไฟล์เก็บถาวร ar
ถูกสร้างขึ้นด้วย ar
ซึ่งเป็นไฟล์เก็บถาวรวัตถุประสงค์ทั่วไปของ GNU ดังนั้น มันไม่ได้ถูกสร้างโดยตัวเชื่อมโยง ไม่มีการเชื่อมโยงเกิดขึ้น ต่อไปนี้คือวิธีที่ ar
รายงานเนื้อหาของไฟล์เก็บถาวร:
$ ar -t libsundry.a
foo.o
bar.o
limerick.txt
นี่คือลักษณะของโคลงในไฟล์เก็บถาวร:
$ rm limerick.txt
$ ar x libsundry.a limerick.txt; cat limerick.txt
There once was a young lady named bright
Whose speed was much faster than light
She set out one day
In a relative way
And returned on the previous night.
ถาม. การใส่ไฟล์อ็อบเจ็กต์สองไฟล์และ ASCII limerick ลงในไฟล์เก็บถาวร ar
เดียวกันจะมีประโยชน์อะไร
ก. เพื่อแสดงให้เห็นว่าฉันทำได้ เพื่อแสดงว่าไฟล์เก็บถาวร ar
เป็นเพียงถุงบรรจุไฟล์
มาดูกันว่า file
มีประโยชน์อย่างไรจาก libsundry.a.
$ file libsundry.a
libsundry.a: current ar archive
ตอนนี้ ผมจะเขียนโปรแกรมสองสามโปรแกรมที่ใช้ libsundry.a
ในการเชื่อมโยง
fooprog.c
extern void foo(void);
int main(void)
{
foo();
return 0;
}
คอมไพล์ ลิงก์ และรันอันนั้น:
$ gcc -c -Wall fooprog.c
$ gcc -o fooprog fooprog.o -L. -lsundry
$ ./fooprog
foo
นั่นมันดอรี่ตัวใหญ่ เห็นได้ชัดว่าตัวเชื่อมโยงไม่ได้ใส่ใจกับการมีอยู่ของ ASCII limerick ใน libsundry.a
เหตุผลก็คือผู้ลิงก์ไม่ได้พยายามลิงก์ limerick.txt
เข้ากับโปรแกรมด้วยซ้ำ มาทำการเชื่อมโยงอีกครั้ง คราวนี้มาพร้อมกับตัวเลือกการวินิจฉัยที่จะแสดงให้เราเห็นว่าไฟล์อินพุตใดบ้างที่ลิงก์อยู่:
$ gcc -o fooprog fooprog.o -L. -lsundry -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
fooprog.o
(./libsundry.a)foo.o
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
มีไลบรารีเริ่มต้นและไฟล์อ็อบเจ็กต์จำนวนมาก แต่ไฟล์อ็อบเจ็กต์เดียวที่ เรา สร้างขึ้นที่ตัวเชื่อมโยงใช้คือ:
fooprog.o
(./libsundry.a)foo.o
ทั้งหมดที่ลิงเกอร์ทำกับ ./libsundry.a
คือนำ foo.o
ออกจากกระเป๋าและเชื่อมโยงมันในโปรแกรม หลังจากลิงก์ fooprog.o
เข้ากับโปรแกรมแล้ว จำเป็นต้องค้นหาคำจำกัดความของ foo
มันมองเข้าไปในกระเป๋า พบคำจำกัดความใน foo.o
จึงนำ foo.o
ออกจากกระเป๋าและเชื่อมโยงเข้ากับโปรแกรม ในการเชื่อมโยง fooprog
,
gcc -o fooprog fooprog.o -L. -lsundry
เป็นการเชื่อมโยงเดียวกันกับ:
$ gcc -o fooprog fooprog.o foo.o
file
พูดอะไรเกี่ยวกับ fooprog
$ file fooprog
fooprog: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), \
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, \
for GNU/Linux 2.6.32, BuildID[sha1]=32525dce7adf18604b2eb5af7065091c9111c16e,
not stripped
นี่คือโปรแกรมที่สองของฉัน:
foobarprog.c
extern void foo(void);
extern void bar(void);
int main(void)
{
foo();
bar();
return 0;
}
คอมไพล์ ลิงก์ และรัน:
$ gcc -c -Wall foobarprog.c
$ gcc -o foobarprog foobarprog.o -L. -lsundry
$ ./foobarprog
foo
bar
และนี่คือการเชื่อมโยงอีกครั้งกับ -trace
:
$ gcc -o foobarprog foobarprog.o -L. -lsundry -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
foobarprog.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
ดังนั้นในครั้งนี้ ไฟล์อ็อบเจ็กต์ของเราที่ตัวลิงก์ใช้คือ:
foobarprog.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
หลังจากลิงก์ foobarprog.o
เข้ากับโปรแกรมแล้ว จำเป็นต้องค้นหาคำจำกัดความของ foo
และ bar
มันมองเข้าไปในกระเป๋า พบคำจำกัดความตามลำดับใน foo.o
และ bar.o
ดังนั้นจึงนำมันออกจากกระเป๋าและเชื่อมโยงเข้ากับโปรแกรม ในการเชื่อมโยง foobarprog
gcc -o foobarprog foobarprog.o -L. -lsundry
เป็นการเชื่อมโยงเดียวกันกับ:
$ gcc -o foobarprog foobarprog.o foo.o bar.o
สรุปทั้งหมดนั้น.. ไฟล์เก็บถาวร ar
เป็นเพียงถุงบรรจุไฟล์ คุณสามารถใช้ไฟล์เก็บถาวร ar
เพื่อเสนอไฟล์ อ็อบเจ็กต์ จำนวนหนึ่งให้กับผู้ลิงก์ เพื่อเลือกไฟล์ที่ต้องการสำหรับการเชื่อมโยงต่อไป มันจะนำไฟล์อ็อบเจ็กต์เหล่านั้นออกจากกระเป๋าและเชื่อมโยงเข้ากับไฟล์เอาท์พุต มันไม่มีประโยชน์อย่างอื่นสำหรับกระเป๋าอย่างแน่นอน กระเป๋าไม่ได้มีส่วนช่วยในการเชื่อมโยงเลย
กระเป๋าช่วยให้ชีวิตของคุณง่ายขึ้นเล็กน้อยโดยช่วยให้คุณไม่ต้องรู้ว่าไฟล์อ็อบเจ็กต์ใดที่คุณต้องการสำหรับการเชื่อมโยงโดยเฉพาะ คุณเพียงแค่ต้องรู้ว่า: พวกเขาอยู่ในกระเป๋าใบนั้น
DSO คืออะไร
มาทำอันหนึ่งกัน
foobar.c
extern void foo(void);
extern void bar(void);
void foobar(void)
{
foo();
bar();
}
เราจะรวบรวมไฟล์ต้นฉบับใหม่นี้:
$ gcc -c -Wall -fPIC foobar.c
จากนั้นสร้าง DSO โดยใช้ foobar.o
และนำ libsundry.a
กลับมาใช้ใหม่
$ gcc -shared -o libfoobar.so foobar.o -L. -lsundry -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbeginS.o
foobar.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/lib/x86_64-linux-gnu/libc.so.6
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/usr/lib/gcc/x86_64-linux-gnu/5/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
นั่นทำให้ DSO libfoobar.so
หมายเหตุ: DSO ถูกสร้างขึ้นโดยตัวเชื่อมโยง มีการเชื่อมโยงเช่นเดียวกับโปรแกรมที่ถูกเชื่อมโยง การเชื่อมโยงของ libfoopar.so
ดูคล้ายกับการเชื่อมโยงของ foobarprog
มาก แต่การเพิ่มตัวเลือก -shared
จะสั่งให้ตัวเชื่อมโยงสร้าง DSO แทนที่จะเป็นโปรแกรม ที่นี่เราจะเห็นว่าไฟล์อ็อบเจ็กต์ของเราที่ใช้โดยการเชื่อมโยงคือ:
foobar.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
ar
ไม่เข้าใจ DSO เลย:
$ ar -t libfoobar.so
ar: libfoobar.so: File format not recognised
แต่ file
ทำ:
$ file libfoobar.so
libfoobar.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), \
dynamically linked, BuildID[sha1]=16747713db620e5ef14753334fea52e71fb3c5c8, \
not stripped
ตอนนี้ถ้าเราเชื่อมโยง foobarprog
ใหม่โดยใช้ libfoobar.so
แทน libsundry.a
:
$ gcc -o foobarprog foobarprog.o -L. -lfoobar -Wl,-trace,--rpath=$(pwd)
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
foobarprog.o
-lfoobar (./libfoobar.so)
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
ที่เราเห็น
foobarprog.o
-lfoobar (./libfoobar.so)
ว่า ./libfoobar.so
ตัวมันเอง ถูกเชื่อมโยงอยู่ ไม่ใช่ไฟล์อ็อบเจ็กต์บางไฟล์ "อยู่ข้างใน" ไม่มีไฟล์อ็อบเจ็กต์อยู่ข้างใน และสิ่งนี้มีส่วนทำให้เกิดการเชื่อมโยงได้อย่างไร สามารถเห็นได้จากการพึ่งพาแบบไดนามิกของโปรแกรม:
$ ldd foobarprog
linux-vdso.so.1 => (0x00007ffca47fb000)
libfoobar.so => /home/imk/develop/so/scrap/libfoobar.so (0x00007fb050eeb000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb050afd000)
/lib64/ld-linux-x86-64.so.2 (0x000055d8119f0000)
โปรแกรมออกมาพร้อมการพึ่งพารันไทม์ใน libfoobar.so
นั่นคือสิ่งที่การเชื่อมโยง DSO ทำ เราจะเห็นได้ว่าการพึ่งพารันไทม์นี้เป็นไปตามที่พอใจ ดังนั้นโปรแกรมจะทำงาน:
$ ./foobarprog
foo
bar
เหมือนเมื่อก่อน
ความจริงที่ว่า DSO และโปรแกรมซึ่งต่างจากไฟล์เก็บถาวร ar
ต่างก็เป็นผลิตภัณฑ์ของตัวเชื่อมโยง แสดงให้เห็นว่า DSO และโปรแกรม เป็น ตัวแปรของสิ่งเดียวกันโดยพื้นฐานแล้ว เอาต์พุต file
ก็แนะนำเช่นกัน DSO และโปรแกรมเป็นทั้งไบนารีของ ELF ที่ตัวโหลด OS สามารถแมปลงในพื้นที่ที่อยู่ของกระบวนการได้ ไม่ใช่แค่ถุงใส่ไฟล์ ไฟล์เก็บถาวร ar
ไม่ใช่ไบนารีของ ELF ใดๆ
ความแตกต่างระหว่างไฟล์ ELF ประเภทโปรแกรมและ ELF ที่ไม่ใช่ประเภทโปรแกรมอยู่ที่ค่าที่แตกต่างกันที่ตัวเชื่อมโยงเขียนลงในโครงสร้าง ELF Header และโครงสร้าง Program Headers ของรูปแบบไฟล์ ELF ความแตกต่างเหล่านี้แนะนำให้ตัวโหลด OS เริ่มต้นกระบวนการใหม่เมื่อโหลดไฟล์ ELF ประเภทโปรแกรม และเพิ่มกระบวนการที่อยู่ระหว่างการสร้างเมื่อโหลดไฟล์ ELF ที่ไม่ใช่โปรแกรม ดังนั้น DSO ที่ไม่ใช่โปรแกรมจึงถูกแมปเข้ากับกระบวนการของโปรแกรมหลัก ความจริงที่ว่าโปรแกรมเริ่มต้นกระบวนการใหม่ต้องการให้โปรแกรมต้องมีจุดเริ่มต้นเดียวซึ่งระบบปฏิบัติการจะผ่านการควบคุม: จุดเริ่มต้นนั้นเป็นฟังก์ชัน main
บังคับในโปรแกรม C หรือ C++ ในทางกลับกัน DSO ที่ไม่ใช่โปรแกรมไม่จำเป็นต้องมีจุดเริ่มต้นบังคับเพียงจุดเดียว สามารถป้อนผ่านฟังก์ชันส่วนกลางใดๆ ที่ส่งออกโดยการเรียกใช้ฟังก์ชันจากโปรแกรมหลัก
แต่จากมุมมองของโครงสร้างไฟล์และเนื้อหา DSO และโปรแกรมก็คล้ายกันมาก เป็นไฟล์ที่สามารถเป็นส่วนประกอบของกระบวนการได้ โปรแกรมจะต้องเป็นส่วนประกอบเริ่มต้น DSO สามารถเป็นองค์ประกอบรองได้
ยังคงเป็นเรื่องปกติสำหรับความแตกต่างเพิ่มเติมที่ต้องทำ: DSO จะต้องประกอบด้วยโค้ดที่ย้ายตำแหน่งได้ทั้งหมด (เนื่องจากไม่รู้ว่า ณ เวลาลิงก์ที่ตัวโหลดอาจจำเป็นต้องวางไว้ในพื้นที่ที่อยู่ของกระบวนการ) ในขณะที่โปรแกรมประกอบด้วยโค้ดที่สมบูรณ์ โหลดด้วยที่อยู่เดียวกันเสมอ แต่ในความเป็นจริง มันค่อนข้างเป็นไปได้ที่จะเชื่อมโยง โปรแกรม ที่สามารถย้ายตำแหน่งได้:
$ gcc -pie -o foobarprog foobarprog.o -L. -lfoobar -Wl,--rpath=$(pwd)
นั่นคือสิ่งที่ -pie
(การดำเนินการตามตำแหน่งอิสระ) ทำที่นี่ แล้ว:
$ file foobarprog
foobarprog: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), ....
file
จะบอกว่า foobarprog
เป็น DSO ซึ่งก็คือ แม้ว่ามันจะยังยังคงเป็นโปรแกรมอยู่:
$ ./foobarprog
foo
bar
และโปรแกรมปฏิบัติการ PIE กำลังได้รับความนิยม ใน Debian 9 และอนุพันธ์ distros (Ubuntu 17.04...) GCC toolchain จะสร้างโปรแกรม PIE ตามค่าเริ่มต้น
หากคุณต้องการความรู้โดยละเอียดเกี่ยวกับรูปแบบไฟล์ ar
และ ELF
นี่คือ รายละเอียดของ ar
รูปแบบ และนี่คือรายละเอียดของรูปแบบ ELF
เหตุใดจึงไม่มีไฟล์ไลบรารีประเภทเดียวที่มาพร้อมกับแฟล็กคอมไพเลอร์ซึ่งระบุว่าควรเชื่อมโยงไลบรารีอย่างไร (คงที่และไดนามิก)
ตัวเลือกระหว่างการเชื่อมโยงแบบไดนามิกและแบบคงที่สามารถควบคุมได้อย่างสมบูรณ์ด้วยตัวเลือกการเชื่อมโยงบรรทัดคำสั่ง ดังนั้นจึงไม่จำเป็นต้องละทิ้งไฟล์เก็บถาวร ar
หรือ DSO หรือสร้างไลบรารีประเภทอื่นเพื่อให้บรรลุเป้าหมายนี้ หากตัวเชื่อมโยงไม่สามารถใช้ไฟล์เก็บถาวร ar
ได้อย่างที่เป็นอยู่ นั่นอาจเป็นความไม่สะดวกอย่างมาก และแน่นอนว่าถ้าตัวเชื่อมโยงไม่สามารถเชื่อมโยง DSO ได้ เราก็จะกลับไปสู่ยุคหินของระบบปฏิบัติการ
person
Mike Kinghan
schedule
05.11.2017