Import Library ทำงานอย่างไร รายละเอียด?


90

ฉันรู้ว่าสิ่งนี้อาจดูค่อนข้างธรรมดาสำหรับคนที่ชอบฟัง แต่ฉันต้องการทำให้มันใส

เมื่อฉันต้องการใช้ Win32 DLL โดยปกติฉันจะเรียก API เช่น LoadLibrary () และ GetProcAdderss () แต่เมื่อเร็ว ๆ นี้ฉันกำลังพัฒนาด้วย DirectX9 และฉันต้องการเพิ่มไฟล์ d3d9.lib , d3dx9.libและอื่น ๆ

ฉันได้ยินมามากพอแล้วว่า LIB ใช้สำหรับการลิงก์แบบคงที่และ DLL สำหรับการลิงก์แบบไดนามิก

ดังนั้นความเข้าใจในปัจจุบันของฉันคือ LIB มีการใช้งานวิธีการและเชื่อมโยงแบบคงที่ในเวลาลิงก์เป็นส่วนหนึ่งของไฟล์ EXE สุดท้าย แม้ว่า DLL จะโหลดแบบไดนามิกที่รันไทม์และไม่ได้เป็นส่วนหนึ่งของไฟล์ EXE สุดท้าย

แต่บางครั้งมีไฟล์ LIB บางไฟล์ที่มาพร้อมกับไฟล์ DLL ดังนั้น:

  • ไฟล์ LIB เหล่านี้มีไว้ทำอะไร?
  • พวกเขาบรรลุสิ่งที่ตั้งใจไว้ได้อย่างไร?
  • มีเครื่องมือใดบ้างที่สามารถให้ฉันตรวจสอบภายในของไฟล์ LIB เหล่านี้ได้

อัปเดต 1

หลังจากการตรวจสอบวิกิพีเดียผมจำได้ว่าไฟล์ LIB เหล่านี้เรียกว่าห้องสมุดนำเข้า แต่ฉันสงสัยว่ามันทำงานอย่างไรกับแอปพลิเคชันหลักของฉันและ DLL ที่จะโหลดแบบไดนามิก

อัปเดต 2

เช่นเดียวกับที่ RBerteig กล่าวว่ามีรหัสต้นขั้วในไฟล์ LIB ที่เกิดพร้อมกับ DLL ลำดับการโทรควรเป็นดังนี้:

แอปพลิเคชันหลักของฉัน -> ต้นขั้วใน LIB -> DLL เป้าหมายจริง

ดังนั้นข้อมูลใดบ้างที่ควรมีอยู่ใน LIBs เหล่านี้? ฉันคิดได้ดังนี้:

  • ไฟล์ LIB ควรมี fullpath ของ DLL ที่เกี่ยวข้อง ดังนั้นจึงสามารถโหลด DLL ได้โดยรันไทม์
  • ที่อยู่สัมพัทธ์ (หรือไฟล์ออฟเซ็ต?) ของแต่ละจุดเริ่มต้นของวิธีการส่งออก DLL ควรถูกเข้ารหัสในต้นขั้ว ดังนั้นจึงสามารถเรียกวิธีการกระโดด / วิธีการที่ถูกต้องได้

ฉันพูดถูกหรือเปล่า มีอะไรเพิ่มเติมไหม

BTW: มีเครื่องมือใดที่สามารถตรวจสอบไลบรารีการนำเข้าได้หรือไม่? ถ้าฉันเห็นมันจะไม่ต้องสงสัยอีกต่อไป


4
ฉันเห็นว่าไม่มีใครตอบคำถามของคุณในส่วนสุดท้ายซึ่งเกี่ยวกับเครื่องมือที่สามารถตรวจสอบไลบรารีการนำเข้าได้ ด้วย Visual C ++ มีอย่างน้อยสองวิธีในการดำเนินการ: lib /list xxx.libและlink /dump /linkermember xxx.lib. ดูคำถาม Stack Overflowนี้
Alan

นอกจากนี้ยังdumpbin -headers xxx.libให้ข้อมูลรายละเอียดเพิ่มเติมเปรียบเทียบกับยูทิลิตี้libและ link
m_katsifarakis

คำตอบ:


105

การลิงก์ไปยังไฟล์ DLL อาจเกิดขึ้นโดยปริยายในเวลาที่คอมไพล์ลิงก์หรืออย่างชัดเจนในขณะรันไทม์ ไม่ว่าจะด้วยวิธีใด DLL จะโหลดลงในพื้นที่หน่วยความจำประมวลผลและจุดเข้าใช้งานที่ส่งออกทั้งหมดจะพร้อมใช้งานสำหรับแอปพลิเคชัน

หากใช้อย่างชัดเจนในขณะทำงานคุณจะใช้LoadLibrary()และGetProcAddress()โหลด DLL ด้วยตนเองและรับตัวชี้ไปยังฟังก์ชันที่คุณต้องการเรียกใช้

หากเชื่อมโยงโดยปริยายเมื่อสร้างโปรแกรมแล้วต้นขั้วสำหรับการส่งออก DLL แต่ละรายการที่ใช้โดยโปรแกรมจะเชื่อมโยงกับโปรแกรมจากไลบรารีนำเข้าและต้นขั้วเหล่านั้นจะได้รับการอัปเดตเมื่อ EXE และ DLL ถูกโหลดเมื่อกระบวนการเริ่มทำงาน (ใช่ฉันง่ายกว่าที่นี่เล็กน้อย ... )

ไม่สมบูรณ์เหล่านั้นต้องมาจากที่ไหนสักแห่งและในห่วงโซ่เครื่องมือของ Microsoft พวกเขามาจากรูปแบบพิเศษของไฟล์ .LIB เรียกว่าห้องสมุดนำเข้า โดยปกติ. LIB ที่จำเป็นจะถูกสร้างขึ้นพร้อมกันกับ DLL และมีต้นขั้วสำหรับแต่ละฟังก์ชันที่ส่งออกจาก DLL

น่าสับสนเวอร์ชันคงที่ของไลบรารีเดียวกันจะถูกจัดส่งเป็นไฟล์. LIB ไม่มีวิธีใดที่จะแยกออกจากกันได้ยกเว้นว่า LIB ที่เป็นไลบรารีนำเข้าสำหรับ DLL มักจะมีขนาดเล็กกว่า (มักจะเล็กกว่ามาก) มากกว่า LIB แบบคงที่ที่ตรงกัน

หากคุณใช้ GCC toolchain โดยบังเอิญคุณไม่จำเป็นต้องนำเข้าไลบรารีเพื่อให้ตรงกับ DLL ของคุณ เวอร์ชันของ Gnu linker ที่พอร์ตไปยัง Windows จะเข้าใจ DLL โดยตรงและสามารถสังเคราะห์ Stubs ที่จำเป็นส่วนใหญ่ได้ทันที

อัปเดต

หากคุณอดไม่ได้ที่จะรู้ว่าน็อตและสลักเกลียวทั้งหมดอยู่ที่ไหนและเกิดอะไรขึ้นจริงๆมีบางอย่างที่ MSDN จะช่วยได้เสมอ บทความของ Matt Pietrek การมองในเชิงลึกในรูปแบบไฟล์ปฏิบัติการแบบพกพา Win32คือภาพรวมที่สมบูรณ์ของรูปแบบของไฟล์ EXE และวิธีการโหลดและเรียกใช้ แม้จะได้รับการอัปเดตให้ครอบคลุม. NET และอื่น ๆ เนื่องจากเดิมเคยปรากฏใน MSDN Magazine ca. พ.ศ. 2545

นอกจากนี้การทราบวิธีเรียนรู้ว่าโปรแกรมใช้ DLLs ใดบ้างจะเป็นประโยชน์ เครื่องมือสำหรับสิ่งนั้นคือ Dependency Walker หรือที่เรียกว่า depend.exe รุ่นของมันจะรวมกับ Visual Studio แต่รุ่นล่าสุดสามารถใช้ได้จากผู้เขียนที่http://www.dependencywalker.com/ สามารถระบุ DLL ทั้งหมดที่ระบุในเวลาลิงก์ (ทั้งการโหลดก่อนกำหนดและการโหลดล่าช้า) และยังสามารถเรียกใช้โปรแกรมและดู DLL เพิ่มเติมใด ๆ ที่โหลดในขณะรันไทม์

อัปเดต 2

ฉันได้เปลี่ยนข้อความก่อนหน้านี้บางส่วนเพื่อชี้แจงในการอ่านซ้ำและใช้เงื่อนไขของการเชื่อมโยงโดยนัยและชัดเจนเพื่อความสอดคล้องกับ MSDN

ดังนั้นเราจึงมีสามวิธีในการทำให้ฟังก์ชันไลบรารีสามารถใช้งานได้โดยโปรแกรม คำถามติดตามผลที่ชัดเจนคือ "ฉันจะเลือกทางไหนได้อย่างไร"

การลิงก์แบบคงที่คือการเชื่อมโยงโปรแกรมจำนวนมาก ไฟล์อ็อบเจ็กต์ทั้งหมดของคุณจะแสดงรายการและรวบรวมไว้ในไฟล์ EXE โดยผู้เชื่อมโยง ระหว่างทางผู้เชื่อมโยงจะดูแลงานเล็ก ๆ น้อย ๆ เช่นการแก้ไขการอ้างอิงถึงสัญลักษณ์ทั่วโลกเพื่อให้โมดูลของคุณสามารถเรียกฟังก์ชันของกันและกันได้ ไลบรารียังสามารถเชื่อมโยงแบบคงที่ ไฟล์อ็อบเจ็กต์ที่ประกอบเป็นไลบรารีจะถูกรวบรวมโดยบรรณารักษ์ในไฟล์. LIB ซึ่งตัวเชื่อมโยงจะค้นหาโมดูลที่มีสัญลักษณ์ที่จำเป็น ผลกระทบอย่างหนึ่งของการลิงก์แบบคงที่คือเฉพาะโมดูลเหล่านั้นจากไลบรารีที่ใช้โดยโปรแกรมเท่านั้นที่เชื่อมโยงกับมัน โมดูลอื่น ๆ จะถูกละเว้น ตัวอย่างเช่นไลบรารีคณิตศาสตร์ C แบบดั้งเดิมมีฟังก์ชันตรีโกณมิติมากมาย แต่ถ้าคุณเชื่อมโยงกับมันและใช้cos()คุณจะไม่ได้รับสำเนาของรหัสsin()หรือtan()เว้นแต่คุณจะเรียกฟังก์ชันเหล่านั้นด้วย สำหรับไลบรารีขนาดใหญ่ที่มีคุณสมบัติมากมายการรวมโมดูลที่เลือกไว้นี้มีความสำคัญ ในหลายแพลตฟอร์มเช่นระบบฝังตัวขนาดทั้งหมดของโค้ดที่พร้อมใช้งานในไลบรารีอาจมีขนาดใหญ่เมื่อเทียบกับพื้นที่ที่มีให้จัดเก็บไฟล์ปฏิบัติการในอุปกรณ์ การจัดการรายละเอียดของการสร้างโปรแกรมสำหรับแพลตฟอร์มเหล่านั้นจะทำได้ยากขึ้น

อย่างไรก็ตามการมีสำเนาของไลบรารีเดียวกันในทุกโปรแกรมที่ทำงานอยู่จะสร้างภาระให้กับระบบที่โดยปกติจะเรียกใช้กระบวนการจำนวนมาก ด้วยระบบหน่วยความจำเสมือนที่เหมาะสมเพจของหน่วยความจำที่มีเนื้อหาเหมือนกันจำเป็นต้องมีอยู่เพียงครั้งเดียวในระบบ แต่สามารถใช้ได้กับหลายกระบวนการ สิ่งนี้สร้างประโยชน์ในการเพิ่มโอกาสที่หน้าที่มีโค้ดมีแนวโน้มที่จะเหมือนกันกับบางเพจในกระบวนการทำงานอื่น ๆ ให้ได้มากที่สุด แต่ถ้าโปรแกรมเชื่อมโยงกับไลบรารีรันไทม์แบบคงที่แต่ละโปรแกรมจะมีการผสมผสานของฟังก์ชันที่แตกต่างกันซึ่งแต่ละฟังก์ชันจัดวางไว้ในการประมวลผลแมปหน่วยความจำในตำแหน่งที่แตกต่างกันและไม่มีโค้ดเพจที่สามารถแชร์ได้มากนักเว้นแต่จะเป็นโปรแกรมที่ทำเองทั้งหมด ทำงานในกระบวนการมากกว่า ดังนั้นแนวคิดเกี่ยวกับ DLL จึงได้รับประโยชน์ที่สำคัญอีกประการหนึ่ง

DLL สำหรับไลบรารีมีฟังก์ชันทั้งหมดพร้อมสำหรับการใช้งานโดยโปรแกรมไคลเอ็นต์ใด ๆ หากหลายโปรแกรมโหลด DLL นั้นก็สามารถแชร์โค้ดเพจได้ทั้งหมด ทุกคนชนะ (ดีจนกว่าคุณจะอัปเดต DLL ด้วยเวอร์ชันใหม่ แต่นั่นไม่ได้เป็นส่วนหนึ่งของเรื่องนี้ Google DLL นรกสำหรับด้านนั้น)

ดังนั้นทางเลือกแรกที่ยิ่งใหญ่ในการวางแผนโครงการใหม่คือระหว่างการเชื่อมโยงแบบไดนามิกและแบบคงที่ ด้วยการเชื่อมโยงแบบคงที่คุณมีไฟล์ที่จะติดตั้งน้อยลงและคุณไม่ได้รับการยกเว้นจากบุคคลภายนอกที่อัปเดต DLL ที่คุณใช้ อย่างไรก็ตามโปรแกรมของคุณมีขนาดใหญ่กว่าและไม่ใช่พลเมืองที่ดีของระบบนิเวศของ Windows ด้วยการเชื่อมโยงแบบไดนามิกคุณมีไฟล์ที่ต้องติดตั้งมากขึ้นคุณอาจมีปัญหากับบุคคลที่สามที่อัปเดต DLL ที่คุณใช้ แต่โดยทั่วไปแล้วคุณจะเป็นมิตรกับกระบวนการอื่น ๆ ในระบบ

ข้อได้เปรียบอย่างมากของ DLL คือสามารถโหลดและใช้งานได้โดยไม่ต้องคอมไพล์ใหม่หรือเชื่อมโยงโปรแกรมหลักอีกครั้ง ซึ่งสามารถอนุญาตให้ผู้ให้บริการไลบรารีบุคคลที่สาม (เช่น Microsoft และ C runtime เป็นต้น) แก้ไขข้อบกพร่องในไลบรารีและแจกจ่าย เมื่อผู้ใช้ติดตั้ง DLL ที่อัปเดตแล้วพวกเขาจะได้รับประโยชน์จากการแก้ไขข้อบกพร่องนั้นทันทีในทุกโปรแกรมที่ใช้ DLL นั้น (เว้นเสียแต่ว่าจะทำให้สิ่งของแตกดู DLL Hell)

ข้อได้เปรียบอื่น ๆ มาจากความแตกต่างระหว่างการโหลดโดยนัยและแบบชัดแจ้ง หากคุณใช้ความพยายามมากขึ้นในการโหลดอย่างชัดเจน DLL อาจไม่มีอยู่ในขณะที่เขียนและเผยแพร่โปรแกรม สิ่งนี้ช่วยให้กลไกส่วนขยายที่สามารถค้นหาและโหลดปลั๊กอินได้เช่น


4
การลบโพสต์ของฉันและการเพิ่มคะแนนโหวตนี้เพราะคุณอธิบายสิ่งต่างๆได้ดีกว่าฉัน)
เมื่อ

2
@RBerteig: ขอบคุณสำหรับคำตอบที่ดี การแก้ไขเพียงเล็กน้อยตามที่นี่ ( msdn.microsoft.com/en-us/library/9yd93633.aspx ) การเชื่อมโยงแบบไดนามิกไปยัง DLL มี 2 ประเภทการเชื่อมโยงโดยนัยเวลาโหลดและการเชื่อมโยงแบบชัดแจ้งในเวลาทำงาน ไม่มีเวลารวบรวมการเชื่อมโยง ตอนนี้ฉันสงสัยว่าอะไรคือความแตกต่างระหว่างStatic Linking แบบเดิม(การเชื่อมโยงไปยังไฟล์ * .lib ที่มีการใช้งานเต็มรูปแบบ) และการเชื่อมโยงแบบไดนามิกเวลาโหลดไปยัง DLL (ผ่านไลบรารีนำเข้า)
smwikipedia

1
ดำเนินการต่อ: ข้อดีข้อเสียของการลิงก์แบบไดนามิกแบบคงที่และเวลาโหลดคืออะไร ดูเหมือนว่าทั้ง 2 วิธีนี้จะโหลดไฟล์ที่จำเป็นทั้งหมดลงในช่องแอดเดรสเมื่อเริ่มต้นกระบวนการ ทำไมเราถึงต้องการ 2 คน? ขอบคุณ.
smwikipedia

1
คุณอาจใช้เครื่องมือเช่น "objdump" เพื่อดูภายในไฟล์. lib และดูว่าเป็นไลบรารีนำเข้าหรือไลบรารีแบบคงที่จริง บน Linux เมื่อคอมไพล์ข้ามไปยังเป้าหมายของ Windows คุณสามารถเรียกใช้ 'ar' หรือ 'nm' บนไฟล์. a ไฟล์ (ไฟล์. lib รุ่น mingw) และโปรดทราบว่า libs นำเข้ามีชื่อไฟล์. o ทั่วไปและไม่มีรหัส (เป็นเพียงคำสั่ง 'jmp') ในขณะที่ libs แบบคงที่มีฟังก์ชันและโค้ดมากมายอยู่ภายใน
อย่าสว่าง

1
การแก้ไขเล็กน้อย: คุณสามารถเชื่อมโยงโดยปริยายในขณะทำงานได้ Linker Support for Delay-Loaded DLLsอธิบายรายละเอียดนี้ สิ่งนี้มีประโยชน์หากคุณต้องการเปลี่ยนเส้นทางการค้นหา DLL แบบไดนามิกหรือจัดการกับความล้มเหลวในการนำเข้าความละเอียดอย่างสง่างาม (เพื่อรองรับฟีเจอร์ใหม่ของระบบปฏิบัติการ แต่ยังคงทำงานบนเวอร์ชันเก่าเป็นต้น)
ระบุได้

5

ไฟล์ไลบรารีอิมพอร์ต. LIB เหล่านี้ถูกใช้ในคุณสมบัติโปรเจ็กต์ต่อไปนี้ Linker->Input->Additional Dependenciesเมื่อสร้าง dll จำนวนมากที่ต้องการข้อมูลเพิ่มเติมในเวลาลิงก์ซึ่งจัดเตรียมโดยไฟล์. LIB ไลบรารีอิมพอร์ต ในตัวอย่างด้านล่างเพื่อไม่ให้ได้รับข้อผิดพลาดตัวเชื่อมโยงฉันจำเป็นต้องอ้างอิงถึง A, B, C และ D ของ dll ผ่านไฟล์ lib (หมายเหตุสำหรับตัวเชื่อมโยงในการค้นหาไฟล์เหล่านี้คุณอาจต้องรวมเส้นทางการปรับใช้ไว้ในที่Linker->General->Additional Library Directoriesอื่นคุณจะได้รับข้อผิดพลาดในการสร้างเกี่ยวกับการไม่พบไฟล์ lib ที่ให้มา)

Linker-> Input-> การพึ่งพาเพิ่มเติม

หากโซลูชันของคุณกำลังสร้างไลบรารีแบบไดนามิกทั้งหมดคุณอาจสามารถหลีกเลี่ยงข้อกำหนดการอ้างอิงที่ชัดเจนนี้ได้โดยอาศัยแฟล็กอ้างอิงที่แสดงภายใต้Common Properties->Framework and Referencesไดอะล็อกแทน แฟล็กเหล่านี้ดูเหมือนจะทำการเชื่อมโยงในนามของคุณโดยอัตโนมัติโดยใช้ไฟล์ * .lib กรอบและการอ้างอิง

อย่างไรก็ตามนี่เป็นไปตามที่กล่าวว่าคุณสมบัติทั่วไปซึ่งไม่ใช่การกำหนดค่าหรือเฉพาะแพลตฟอร์ม หากคุณต้องการสนับสนุนสถานการณ์การสร้างแบบผสมเช่นเดียวกับในแอปพลิเคชันของเราเรามีการกำหนดค่าการสร้างเพื่อแสดงผลโครงสร้างแบบคงที่และการกำหนดค่าพิเศษที่สร้างชุดประกอบย่อยที่มีข้อ จำกัด ซึ่งถูกปรับใช้เป็นไลบรารีแบบไดนามิก ฉันใช้แฟล็กUse Library Dependency Inputs และLink Library Dependenciesตั้งค่าเป็นจริงในหลายกรณีเพื่อให้ได้สิ่งต่างๆมาสร้างและในภายหลังก็ตระหนักว่าจะทำให้สิ่งต่าง ๆ ง่ายขึ้น แต่เมื่อแนะนำโค้ดของฉันไปยังโครงสร้างแบบคงที่ฉันได้แนะนำคำเตือนตัวเชื่อมโยงจำนวนมากและการสร้างนั้นช้าอย่างไม่น่าเชื่อสำหรับการสร้างแบบคง ฉันแนะนำคำเตือนเหล่านี้มากมาย ...

warning LNK4006: "bool __cdecl XXX::YYY() already defined in CoreLibrary.lib(JSource.obj); second definition ignored  D.lib(JSource.obj)

และฉันใช้ข้อกำหนดแบบแมนนวลAdditional Dependenciesเพื่อตอบสนองตัวเชื่อมโยงสำหรับการสร้างแบบไดนามิกในขณะที่ทำให้ตัวสร้างแบบคงที่มีความสุขโดยไม่ใช้คุณสมบัติทั่วไปที่ทำให้พวกเขาช้าลง เมื่อฉันปรับใช้ชุดย่อยแบบไดนามิกฉันปรับใช้เฉพาะไฟล์ dll เนื่องจากไฟล์ lib เหล่านี้ใช้ในเวลาลิงก์เท่านั้นไม่ใช่ที่รันไทม์


3

ไลบรารีมีสามประเภท: ไลบรารีแบบคงที่แชร์และโหลดแบบไดนามิก

ไลบรารีแบบคงที่เชื่อมโยงกับรหัสที่เฟสการเชื่อมโยงดังนั้นจึงอยู่ในไฟล์ปฏิบัติการได้จริงซึ่งแตกต่างจากไลบรารีที่ใช้ร่วมกันซึ่งมีเพียงต้นขั้ว (สัญลักษณ์) ที่จะค้นหาในไฟล์ไลบรารีที่ใช้ร่วมกันซึ่งโหลดในเวลาทำงานก่อน เรียกฟังก์ชันหลัก

สิ่งที่โหลดแบบไดนามิกนั้นเหมือนกับไลบรารีที่ใช้ร่วมกันยกเว้นว่าจะโหลดเมื่อใดและหากจำเป็นต้องใช้โค้ดที่คุณเขียน


@ ขอบคุณ zacsek. แต่ฉันไม่แน่ใจเกี่ยวกับคำชี้แจงของคุณเกี่ยวกับไลบรารีที่ใช้ร่วมกัน
smwikipedia

@smwikipedia: Linux มีพวกเขาฉันใช้มันดังนั้นพวกมันจึงมีอยู่จริงที่สุด อ่านเพิ่มเติม: en.wikipedia.org/wiki/Library_(computing)
ZoltánSzőcs

3
ความแตกต่างที่ลึกซึ้ง ไลบรารีที่ใช้ร่วมกันและไดนามิกเป็นทั้งไฟล์ DLL ความแตกต่างคือเมื่อโหลด ไลบรารีที่ใช้ร่วมกันถูกโหลดโดย OS พร้อมกับ EXE ไลบรารีแบบไดนามิกถูกโหลดโดยการเรียกโค้ดLoadLibrary()และ API ที่เกี่ยวข้อง
RBerteig

ฉันอ่านจาก [1] ว่า DLL คือการนำแนวคิด Shared Library ของ Microsoft มาใช้ [1]: en.wikipedia.org/wiki/Dynamic-link_library#Import_libraries
smwikipedia

ฉันไม่เห็นด้วยว่ามันเป็นความแตกต่างเล็กน้อยจากมุมมองการเขียนโปรแกรมมันสร้างความแตกต่างอย่างมากไม่ว่าไลบรารีที่ใช้ร่วมกันจะโหลดแบบ dinamically หรือไม่ (ถ้าโหลดแบบดินามิกคุณต้องเพิ่มโค้ดสำเร็จรูปเพื่อเข้าถึงฟังก์ชัน)
ZoltánSzőcs

2
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.