1) ไบนารีที่คอมไพล์ถูกเขียนไปที่ prom / flash yes USB, ซีเรียล, i2c, jtag ฯลฯ ขึ้นอยู่กับอุปกรณ์ว่าอุปกรณ์นั้นรองรับอะไรบ้างไม่เกี่ยวข้องกับการทำความเข้าใจกระบวนการบูต
2) กรณีนี้ไม่เป็นความจริงสำหรับไมโครคอนโทรลเลอร์กรณีการใช้งานหลักคือการมีคำแนะนำในรอม / แฟลชและข้อมูลในหน่วยความจำ ไม่ว่าสถาปัตยกรรมจะเป็นอย่างไร สำหรับผู้ที่ไม่ใช่ไมโครคอนโทรลเลอร์, พีซี, แล็ปท็อป, เซิร์ฟเวอร์, โปรแกรมของคุณจะถูกคัดลอกจาก non-volatile (disk) ไปที่ ram จากนั้นรันจากที่นั่น ไมโครคอนโทรลเลอร์บางตัวอนุญาตให้คุณใช้ RAM เช่นกันแม้แต่ตัวที่เรียกฮาร์วาร์ดแม้ว่ามันจะเป็นการละเมิดคำจำกัดความก็ตาม ไม่มีอะไรเกี่ยวกับฮาร์วาร์ที่ป้องกันคุณจากการทำแผนที่ ram เข้าไปในคำสั่งคุณเพียงแค่ต้องมีกลไกในการรับคำแนะนำที่นั่นหลังจากไฟฟ้าดับ (ซึ่งเป็นการละเมิดคำจำกัดความ แต่ระบบฮาร์วาร์ดจะต้องมีประโยชน์อื่น ๆ กว่าในฐานะไมโครคอนโทรลเลอร์)
3) ประเภทของ
ซีพียูแต่ละตัว "บูท" ในรูปแบบที่กำหนดไว้ วิธีที่พบมากที่สุดคือตารางเวกเตอร์ที่ที่อยู่สำหรับคำแนะนำแรกในการเรียกใช้หลังจากเปิดเครื่องอยู่ในเวกเตอร์รีเซ็ตที่อยู่ที่ฮาร์ดแวร์อ่านแล้วใช้ที่อยู่นั้นเพื่อเริ่มทำงาน วิธีทั่วไปคือการให้ตัวประมวลผลเริ่มต้นทำงานโดยไม่มีตารางเวกเตอร์ที่ที่อยู่ที่รู้จักกันดี บางครั้งชิพจะมี "สายรัด" ซึ่งเป็นพินที่คุณสามารถผูกได้สูงหรือต่ำก่อนที่จะทำการรีเซทซึ่งตรรกะนั้นใช้ในการบูตวิธีต่างๆ คุณต้องแยกซีพียูเองซึ่งเป็นแกนประมวลผลออกจากส่วนที่เหลือของระบบ ทำความเข้าใจเกี่ยวกับวิธีการทำงานของซีพียูจากนั้นเข้าใจว่าผู้ออกแบบชิป / ระบบมีที่อยู่การตั้งค่าตัวถอดรหัสรอบนอกของซีพียูเพื่อให้บางส่วนของพื้นที่แอดเดรส cpus สื่อสารกับแฟลช และบางส่วนมี RAM และบางส่วนมีอุปกรณ์ต่อพ่วง (uart, i2c, spi, gpio ฯลฯ ) คุณสามารถใช้ซีพียูแกนเดียวกันถ้าคุณต้องการและห่อมันแตกต่างกัน นี่คือสิ่งที่คุณจะได้รับเมื่อคุณซื้อแขนหรือสิ่งที่มีพื้นฐานมาจาก แขนและ mips สร้างแกน cpu ที่ผู้คนซื้อและห่อสิ่งของของตัวเองด้วยเหตุผลหลายประการที่พวกเขาไม่ได้ทำให้สิ่งที่เข้ากันได้จากแบรนด์ที่แบรนด์ ซึ่งเป็นเหตุผลที่ไม่ค่อยสามารถถามคำถามแขนทั่วไปเมื่อมันมาถึงอะไรนอกแกน
ไมโครคอนโทรลเลอร์พยายามเป็นระบบบนชิปดังนั้นหน่วยความจำที่ไม่ลบเลือน (แฟลช / รอม) ระเหย (sram) และ cpu ทั้งหมดอยู่บนชิปเดียวกันพร้อมกับอุปกรณ์ต่อพ่วง แต่ชิปได้รับการออกแบบภายในเพื่อให้แฟลชถูกแมปลงในพื้นที่ที่อยู่ของ cpu ที่ตรงกับลักษณะการบูตของ cpu นั้น หากตัวอย่าง cpu มีเวกเตอร์รีเซ็ตที่แอดเดรส 0xFFFC จะต้องมี flash / rom ที่ตอบกลับไปยังที่อยู่ที่เราสามารถโปรแกรมผ่าน 1) พร้อมกับ flash / rom ในพื้นที่ที่อยู่ของโปรแกรมที่มีประโยชน์ ผู้ออกแบบชิปอาจเลือกให้แฟลช 0x1000 ไบต์เริ่มต้นที่ 0xF000 เพื่อตอบสนองความต้องการเหล่านั้น และบางทีพวกเขาวาง ram จำนวนหนึ่งที่ที่อยู่ต่ำกว่าหรืออาจจะเป็น 0x0000 และอุปกรณ์ต่อพ่วงอยู่ตรงกลาง
สถาปัตยกรรมอื่นของ cpu อาจเริ่มดำเนินการที่ศูนย์ที่อยู่ดังนั้นพวกเขาจะต้องทำตรงข้ามวางแฟลชเพื่อที่จะตอบช่วงที่อยู่รอบศูนย์ พูด 0x0000 ถึง 0x0FFF เช่น และจากนั้นใส่หน่วยความจำไปที่อื่น
ผู้ออกแบบชิปรู้วิธีที่ cpu บูทและพวกเขาวางที่จัดเก็บข้อมูลแบบไม่ลบเลือนที่นั่น (แฟลช / รอม) จากนั้นขึ้นอยู่กับกลุ่มซอฟต์แวร์ที่จะเขียนรหัสบูตเพื่อให้ตรงกับพฤติกรรมที่รู้จักกันดีของ cpu นั้น คุณต้องวางที่อยู่เวกเตอร์รีเซ็ตในเวกเตอร์รีเซ็ตและรหัสการบูตของคุณตามที่อยู่ที่คุณกำหนดไว้ในเวกเตอร์รีเซ็ต Toolchain สามารถช่วยคุณได้อย่างมากที่นี่ บางครั้งอาจใช้จุดและคลิก ides หรือกล่องทรายอื่น ๆ ที่พวกเขาอาจทำงานให้คุณได้ทั้งหมดที่คุณทำคือเรียก apis ในภาษาระดับสูง (C)
แต่อย่างไรก็ตามมันจะเสร็จสิ้นโปรแกรมที่โหลดเข้ามาในแฟลช / รอมจะต้องตรงกับพฤติกรรมการบูทเดินสายของซีพียู ก่อนส่วน C ของโปรแกรม main () และถ้าคุณใช้ main เป็นจุดเริ่มต้นของคุณต้องทำบางสิ่ง โปรแกรมเมอร์ AC สมมติว่าเมื่อประกาศตัวแปรที่มีค่าเริ่มต้นพวกเขาคาดหวังว่าจะใช้งานได้จริง ตัวแปรอื่นที่ไม่ใช่ const อยู่ใน ram แต่ถ้าคุณมีอันที่มีค่าเริ่มต้นที่ค่าเริ่มต้นต้องอยู่ใน ram ที่ไม่ลบเลือน ดังนั้นนี่คือเซ็กเมนต์. data และ C bootstrap ต้องคัดลอกข้อมูล. data จากแฟลชไปยัง ram (ซึ่งปกติจะถูกกำหนดให้คุณโดย toolchain) ตัวแปรโกลบอลที่คุณประกาศโดยไม่มีค่าเริ่มต้นจะถือว่าเป็นศูนย์ก่อนที่โปรแกรมของคุณจะเริ่มต้นแม้ว่าคุณจะไม่ควรสรุปและผู้รวบรวมคอมไพเลอร์บางคนเริ่มที่จะเตือนเกี่ยวกับตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้น นี่คือเซ็กเมนต์. bss และศูนย์ bootstrap ของ C ที่อยู่ใน ram, เนื้อหา, ศูนย์ไม่จำเป็นต้องเก็บไว้ในหน่วยความจำที่ไม่ลบเลือน แต่ที่อยู่เริ่มต้นและจำนวนเท่าไหร่ toolchain อีกครั้งช่วยคุณได้มากที่นี่ และท้ายสุดขั้นต่ำสุดคือคุณต้องตั้งค่าตัวชี้สแต็กเนื่องจากโปรแกรม C คาดว่าจะสามารถมีตัวแปรโลคัลและเรียกใช้ฟังก์ชันอื่น ๆ ได้ จากนั้นอาจจะมีบางสิ่งที่เฉพาะเจาะจงของชิปอื่นทำหรือเราปล่อยให้ส่วนที่เหลือของสิ่งที่เฉพาะชิปเกิดขึ้นในซี ไม่จำเป็นต้องเก็บไว้ในหน่วยความจำที่ไม่ลบเลือน แต่ที่อยู่เริ่มต้นและจำนวนเท่าไหร่ toolchain อีกครั้งช่วยคุณได้มากที่นี่ และท้ายสุดขั้นต่ำสุดคือคุณต้องตั้งค่าตัวชี้สแต็กเนื่องจากโปรแกรม C คาดว่าจะสามารถมีตัวแปรโลคัลและเรียกใช้ฟังก์ชันอื่น ๆ ได้ จากนั้นอาจจะมีบางสิ่งที่เฉพาะเจาะจงของชิปอื่นทำหรือเราปล่อยให้ส่วนที่เหลือของสิ่งที่เฉพาะชิปเกิดขึ้นในซี ไม่จำเป็นต้องเก็บไว้ในหน่วยความจำที่ไม่ลบเลือน แต่ที่อยู่เริ่มต้นและจำนวนเท่าไหร่ toolchain อีกครั้งช่วยคุณได้มากที่นี่ และท้ายสุดขั้นต่ำสุดคือคุณต้องตั้งค่าตัวชี้สแต็กเนื่องจากโปรแกรม C คาดว่าจะสามารถมีตัวแปรโลคัลและเรียกใช้ฟังก์ชันอื่น ๆ ได้ จากนั้นอาจจะมีบางสิ่งที่เฉพาะเจาะจงของชิปอื่นทำหรือเราปล่อยให้ส่วนที่เหลือของสิ่งที่เฉพาะชิปเกิดขึ้นในซี
แกนชุด Cortex-m จากแขนจะทำสิ่งนี้สำหรับคุณตัวชี้สแต็คอยู่ในตารางเวกเตอร์มีเวกเตอร์รีเซ็ตชี้ไปที่รหัสที่จะเรียกใช้หลังจากรีเซ็ตดังนั้นนอกเหนือจากสิ่งที่คุณต้องทำ เพื่อสร้างตารางเวกเตอร์ (ซึ่งคุณมักจะใช้ asm สำหรับต่อไป) คุณสามารถไปที่ C บริสุทธิ์โดยไม่ต้อง asm ตอนนี้คุณไม่ได้รับ. data ที่คัดลอกมาหรือ. bbs ของคุณเป็นศูนย์ดังนั้นคุณต้องทำด้วยตัวเองถ้าคุณต้องการที่จะลองโดยไม่ต้องใช้ asm กับบางสิ่งบางอย่างจากเยื่อหุ้มสมอง คุณลักษณะที่ใหญ่กว่าไม่ใช่เวกเตอร์รีเซ็ต แต่เวกเตอร์ขัดจังหวะโดยที่ฮาร์ดแวร์ตามแขนที่แนะนำให้ใช้การเรียกแบบ C และสงวนการลงทะเบียนให้คุณและใช้ผลตอบแทนที่ถูกต้องสำหรับเวกเตอร์นั้นดังนั้นคุณไม่จำเป็นต้องห่อ asm ที่เหมาะสมรอบตัวจัดการแต่ละตัว หรือมีคำสั่งเฉพาะ toolchain สำหรับเป้าหมายของคุณเพื่อให้ toolchain เป็นคำสั่งให้คุณ)
อาจมีการใช้ชิปเฉพาะสิ่งไมโครคอนโทรลเลอร์มักใช้ในระบบที่ใช้แบตเตอรีดังนั้นพลังงานต่ำดังนั้นบางส่วนจึงรีเซ็ตโดยที่อุปกรณ์ต่อพ่วงส่วนใหญ่ปิดอยู่และคุณต้องเปิดระบบย่อยเหล่านี้เพื่อให้สามารถใช้งานได้ . Uarts, gpios ฯลฯ มักใช้ความเร็วสัญญาณนาฬิกาต่ำโดยตรงจากคริสตัลหรือออสซิลเลเตอร์ภายใน และการออกแบบระบบของคุณอาจแสดงว่าคุณต้องการนาฬิกาที่เร็วขึ้นดังนั้นคุณจึงเริ่มต้นได้ นาฬิกาของคุณอาจเร็วเกินไปสำหรับแฟลชหรือ RAM ดังนั้นคุณอาจจำเป็นต้องเปลี่ยนสถานะการรอก่อนที่จะเพิ่มนาฬิกา อาจจำเป็นต้องตั้งค่า uart หรือ usb หรืออินเทอร์เฟซอื่น ๆ แอปพลิเคชันของคุณสามารถทำสิ่งนั้นได้
เดสก์ท็อปคอมพิวเตอร์แล็ปท็อปเซิร์ฟเวอร์และไมโครคอนโทรลเลอร์ไม่แตกต่างกันในวิธีการบูต / ทำงาน ยกเว้นว่าพวกเขาไม่ได้อยู่ในชิปตัวเดียวเป็นส่วนใหญ่ โปรแกรมไบออสมักจะอยู่บนชิปแฟลช / รอมแยกต่างหากจากซีพียู แม้ว่าเมื่อเร็ว ๆ นี้ x86 cpus จะดึงสิ่งที่เคยเป็นชิปสนับสนุนมาใส่ในแพ็คเกจเดียวกัน (pcie controllers ฯลฯ ) มากขึ้นเรื่อย ๆ แต่คุณยังคงมี ram และ rom off chip ส่วนใหญ่ แต่ก็ยังเป็นระบบและยังคงทำงานได้อย่างแน่นอน เหมือนกันในระดับสูง กระบวนการบูต cpu เป็นที่รู้จักกันดีผู้ออกแบบบอร์ดวางแฟลช / รอมในพื้นที่ที่อยู่ที่บู๊ต cpu โปรแกรมนั้น (ส่วนหนึ่งของ BIOS บน x86 pc) ทำทุกสิ่งที่กล่าวมาข้างต้นมันเริ่มต้นขึ้นกับอุปกรณ์ต่อพ่วงต่าง ๆ มันเริ่มต้น dram, แจกแจง pcie bus และอื่น ๆ ผู้ใช้สามารถกำหนดค่าได้ค่อนข้างบ่อยตามการตั้งค่าไบออสหรือสิ่งที่เราใช้เรียกการตั้งค่า cmos เพราะในเวลานั้นเป็นเทคโนโลยีที่ใช้ ไม่สำคัญมีการตั้งค่าผู้ใช้ที่คุณสามารถไปและเปลี่ยนเพื่อบอกรหัสการบูตไบออสว่าจะเปลี่ยนแปลงสิ่งที่ทำ
ต่างคนจะใช้คำศัพท์ต่างกัน ชิปบูตนั่นคือรหัสแรกที่ทำงาน บางครั้งเรียกว่า bootstrap bootloader ที่มีตัวโหลดคำมักจะหมายความว่าหากคุณไม่ทำอะไรเลยที่จะเข้าไปยุ่งมันเป็น bootstrap ซึ่งจะพาคุณจากการบูททั่วไปไปสู่สิ่งที่ใหญ่กว่าแอพพลิเคชั่นหรือระบบปฏิบัติการของคุณ แต่ส่วนโหลดเดอร์หมายความว่าคุณสามารถขัดจังหวะกระบวนการบูตแล้วอาจโหลดโปรแกรมทดสอบอื่น ๆ หากคุณเคยใช้ uboot เช่นในระบบลินุกซ์ในตัวคุณสามารถกดปุ่มและหยุดการบู๊ตปกติจากนั้นคุณสามารถดาวน์โหลดเคอร์เนลทดสอบลงในหน่วยความจำและบูตแทนหนึ่งในแฟลชหรือคุณสามารถดาวน์โหลด โปรแกรมของตัวเองหรือคุณสามารถดาวน์โหลดเคอร์เนลใหม่จากนั้นให้ bootloader เขียนเป็นแฟลชเพื่อให้ครั้งต่อไปที่คุณบูตมันรันสิ่งใหม่
เท่าที่ซีพียูตัวประมวลผลหลักซึ่งไม่รู้จัก ram จากแฟลชจากอุปกรณ์ต่อพ่วง ไม่มีความคิดของ bootloader ระบบปฏิบัติการแอปพลิเคชัน มันเป็นเพียงลำดับของคำสั่งที่ป้อนเข้าสู่ cpu ที่จะดำเนินการ นี่คือข้อกำหนดของซอฟต์แวร์เพื่อแยกงานการเขียนโปรแกรมที่แตกต่างจากงานอื่น แนวคิดของซอฟต์แวร์จากกันและกัน
ไมโครคอนโทรลเลอร์บางตัวมี bootloader แยกต่างหากที่จัดทำโดยผู้จำหน่ายชิปในแฟลชแยกหรือพื้นที่แฟลชแยกต่างหากที่คุณอาจไม่สามารถแก้ไขได้ ในกรณีนี้มักจะมีพินหรือชุดพิน (ฉันเรียกพวกเขาว่าสายรัด) ว่าถ้าคุณผูกมันสูงหรือต่ำก่อนที่จะรีเซตคุณจะบอกตรรกะและ / หรือ bootloader ว่าจะทำอย่างไรตัวอย่างเช่นการรวมกันของสายรัดหนึ่งอาจ บอกให้ชิปรัน bootloader นั้นและรอ uart เพื่อให้ข้อมูลถูกตั้งโปรแกรมในแฟลช ติดตั้งสายรัดไว้ในลักษณะอื่นและโปรแกรมของคุณจะไม่บู๊ตจากผู้ขายชิป bootloader เพื่อให้สามารถตั้งโปรแกรมฟิลด์ของชิปหรือกู้คืนจากการหยุดทำงานของโปรแกรม บางครั้งมันเป็นเพียงตรรกะที่บริสุทธิ์ที่ช่วยให้คุณสามารถตั้งโปรแกรมแฟลช นี่เป็นเรื่องธรรมดาในทุกวันนี้
เหตุผลที่ไมโครคอนโทรลเลอร์ส่วนใหญ่มีแฟลชมากกว่า RAM คือกรณีที่ใช้งานหลักคือเรียกใช้โปรแกรมโดยตรงจากแฟลชและมี RAM เพียงพอที่จะครอบคลุมสแต็กและตัวแปร แม้ว่าในบางกรณีคุณสามารถเรียกใช้โปรแกรมจากหน่วยความจำที่คุณต้องรวบรวมสิทธิและเก็บในแฟลชแล้วคัดลอกก่อนที่จะโทร
แก้ไข
flash.s
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
notmain.c
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
return(0);
}
flash.ld
MEMORY
{
bob : ORIGIN = 0x00000000, LENGTH = 0x1000
ted : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.rodata : { *(.rodata*) } > bob
.bss : { *(.bss*) } > ted
.data : { *(.bss*) } > ted AT > bob
}
ดังนั้นนี่คือตัวอย่างสำหรับ cortex-m0, cortex-ms ทั้งหมดทำงานเหมือนกับที่ตัวอย่างนี้ไป ตัวอย่างชิปนี้มีแอพพลิเคชั่นแฟลชที่แอดเดรส 0x00000000 ในพื้นที่แอดเดรสแขนและ RAM ที่ 0x20000000
วิธีที่ cortex-m boots คือคำขนาด 32 บิตที่แอดเดรส 0x0000 คือที่อยู่เพื่อเริ่มต้นตัวชี้สแต็ก ฉันไม่ต้องการสแต็คจำนวนมากสำหรับตัวอย่างนี้ดังนั้น 0x20001000 จะพอเพียงเห็นได้ชัดว่าต้องมี ram ด้านล่างที่อยู่นั้น (วิธีที่แขนดันมันลบก่อนแล้วจึงผลักดังนั้นถ้าคุณตั้ง 0x20001000 รายการแรกบนสแต็ค คุณไม่จำเป็นต้องใช้ 0x2000FFFC) คำ 32 บิตที่ที่อยู่ 0x0004 เป็นที่อยู่ของตัวจัดการรีเซ็ตโดยทั่วไปคือรหัสแรกที่ทำงานหลังจากรีเซ็ต จากนั้นก็มีตัวขัดจังหวะและตัวจัดการเหตุการณ์ที่เฉพาะเจาะจงกับคอร์เท็กซ์เอ็มคอร์และชิปซึ่งอาจมากถึง 128 หรือ 256 ถ้าคุณไม่ใช้พวกเขาแล้วคุณไม่จำเป็นต้องตั้งค่าตารางสำหรับพวกเขา วัตถุประสงค์
ฉันไม่จำเป็นต้องจัดการกับ. data หรือ. bss ในตัวอย่างนี้เพราะฉันรู้แล้วว่าไม่มีอะไรในเซ็กเมนต์เหล่านั้นโดยดูที่โค้ด หากมีฉันจะจัดการกับมันและจะในไม่กี่วินาที
ดังนั้นการตั้งค่าสแต็กตรวจสอบ. data ได้รับการดูแลตรวจสอบ, .bss ตรวจสอบดังนั้นสิ่ง C bootstrap เสร็จแล้วสามารถแยกไปยังฟังก์ชันรายการสำหรับ C. เพราะคอมไพเลอร์บางคนจะเพิ่มขยะพิเศษหากพวกเขาเห็นฟังก์ชั่น main () และระหว่างทางไป main ฉันไม่ใช้ชื่อที่แน่นอนฉันใช้ notmain () ที่นี่เป็นจุดเข้า C ของฉัน ดังนั้นตัวจัดการการรีเซ็ตจึงเรียก notmain () ดังนั้นถ้า / เมื่อ notmain () ส่งคืนมันจะหยุดซึ่งเป็นเพียงการวนซ้ำไม่สิ้นสุดอาจมีชื่อไม่ดี
ฉันเชื่อมั่นในการควบคุมเครื่องมือผู้คนจำนวนมากไม่ได้ แต่สิ่งที่คุณจะพบคือนักพัฒนาโลหะเปลือยแต่ละคนทำสิ่งของเขา / เธอเพราะมีอิสระเต็มที่ใกล้ไม่ จำกัด จากระยะไกลเหมือนกับที่คุณทำแอพหรือหน้าเว็บ . พวกเขาทำสิ่งของตนเองอีกครั้ง ฉันชอบที่จะมีรหัส bootstrap และ linker script ของตัวเอง คนอื่น ๆ พึ่งพา toolchain หรือเล่นใน sandbox ของผู้ขายซึ่งงานส่วนใหญ่ทำโดยคนอื่น (และหากมีบางสิ่งที่แตกคุณอยู่ในโลกแห่งความเจ็บปวดและสิ่งที่ทำด้วยโลหะเปลือยแตกบ่อยครั้ง
ดังนั้นการรวบรวมรวบรวมและเชื่อมโยงกับเครื่องมือ gnu ฉันได้รับ:
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
ดังนั้น bootloader จะรู้ได้อย่างไรว่าอยู่ที่ไหน เพราะคอมไพเลอร์ทำงานได้ดี ในกรณีแรกแอสเซมเบลอร์สร้างโค้ดสำหรับ flash.s และการทำเช่นนั้นรู้ว่าเลเบลอยู่ที่ไหน (เลเบลเป็นที่อยู่เหมือนกับชื่อฟังก์ชันหรือชื่อตัวแปร ฯลฯ ) ดังนั้นฉันจึงไม่ต้องนับไบต์และเติมเวกเตอร์ ตารางด้วยตนเองฉันใช้ชื่อฉลากและแอสเซมเบลอร์ทำเพื่อฉัน ตอนนี้คุณถามว่าการรีเซ็ตเป็นที่อยู่ 0x14 ทำไมแอสเซมเบลอร์ใส่ 0x15 ลงในตารางเวกเตอร์ นี่คือ cortex-m และมันบูทและทำงานในโหมดนิ้วโป้งเท่านั้น ด้วย ARM เมื่อคุณแยกสาขาไปยังที่อยู่หากการแยกเป็นโหมดนิ้วหัวแม่มือจำเป็นต้องตั้งค่า lsbit หากโหมดแขนตั้งค่าใหม่ ดังนั้นคุณต้องตั้งค่าบิตนั้นเสมอ ฉันรู้ว่าเครื่องมือและโดยการวาง .umb_func ก่อนที่จะติดป้ายถ้ามีการใช้ป้ายกำกับนั้นในตารางเวกเตอร์หรือสำหรับการแตกแขนงหรืออะไรก็ตาม Toolchain รู้วิธีตั้งค่า lsbit ดังนั้นจึงมีที่นี่ 0x14 | 1 = 0x15 ในทำนองเดียวกันสำหรับการแขวน ตอนนี้ disassembler ไม่แสดง 0x1D สำหรับการโทรถึง notmain () แต่ไม่ต้องกังวลว่าเครื่องมือได้สร้างคำสั่งอย่างถูกต้องแล้ว
ตอนนี้โค้ดนั้นไม่ใช่ตัวแปรหลักในท้องถิ่นนั้นไม่ได้ใช้ แต่เป็นรหัสตาย คอมไพเลอร์แม้คอมเม้นท์ในข้อเท็จจริงนั้นโดยบอกว่า y ถูกตั้งค่า แต่ไม่ได้ใช้
สังเกตพื้นที่ที่อยู่สิ่งเหล่านี้ทั้งหมดเริ่มต้นจากที่อยู่ 0x0000 และไปจากที่นั่นดังนั้นตารางเวกเตอร์จะถูกวางไว้อย่างถูกต้องพื้นที่. text หรือ program ยังถูกวางไว้อย่างถูกต้องวิธีที่ฉันได้รับ flash.s หน้ารหัสของ notmain.c การรู้จักเครื่องมือต่าง ๆ ข้อผิดพลาดทั่วไปคือการไม่ได้รับสิทธิและความผิดพลาดและการเผาไหม้อย่างหนัก IMO คุณต้องแยกย้ายกันเพื่อให้แน่ใจว่าสิ่งต่างๆถูกวางไว้ก่อนที่คุณจะบูตในครั้งแรกเมื่อคุณมีสิ่งต่าง ๆ ในที่ที่ถูกต้องคุณไม่จำเป็นต้องตรวจสอบทุกครั้ง เพียงแค่สำหรับโครงการใหม่หรือถ้าพวกเขาแขวน
ทีนี้บางสิ่งที่น่าประหลาดใจก็คือไม่มีเหตุผลอะไรเลยที่จะคาดหวังว่าคอมไพเลอร์สองตัวใด ๆ ที่จะสร้างเอาต์พุตเดียวกันจากอินพุตเดียวกัน หรือแม้แต่คอมไพเลอร์ตัวเดียวกันที่มีการตั้งค่าต่างกัน ด้วยการใช้ clang, llvm คอมไพเลอร์ที่ฉันได้รับเอาท์พุททั้งสองนี้มีและไม่มีการปรับให้เหมาะสม
เพิ่มประสิทธิภาพ llvm / เสียงดังกราว
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
ไม่ปรับให้เหมาะสม
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: b082 sub sp, #8
1e: 2001 movs r0, #1
20: 9001 str r0, [sp, #4]
22: 2002 movs r0, #2
24: 9000 str r0, [sp, #0]
26: 2000 movs r0, #0
28: b002 add sp, #8
2a: 4770 bx lr
เพื่อเป็นการโกหกที่คอมไพเลอร์ได้เพิ่มประสิทธิภาพการเพิ่ม แต่มันจัดสรรสองรายการในสแต็กสำหรับตัวแปรเนื่องจากสิ่งเหล่านี้เป็นตัวแปรเฉพาะที่พวกเขาอยู่ในหน่วยความจำ แต่บนสแต็กไม่ใช่ที่ที่อยู่คงที่ การเปลี่ยนแปลง แต่คอมไพเลอร์รู้ว่ามันสามารถคำนวณ y ในเวลาคอมไพล์และไม่มีเหตุผลที่จะคำนวณมันในเวลาทำงานดังนั้นมันจึงวาง 1 ในพื้นที่สแต็กที่จัดสรรสำหรับ x และ 2 สำหรับพื้นที่สแต็กที่จัดสรรสำหรับ y คอมไพเลอร์ "จัดสรร" พื้นที่นี้ด้วยตารางภายในฉันประกาศ stack บวก 0 สำหรับตัวแปร y และ stack บวก 4 สำหรับตัวแปร x คอมไพเลอร์สามารถทำอะไรก็ได้ที่มันต้องการตราบใดที่โค้ดที่ใช้นั้นเป็นไปตามมาตรฐาน C หรือ expetations ของโปรแกรมเมอร์ C ไม่มีเหตุผลใดที่คอมไพเลอร์ต้องปล่อยให้ x อยู่ที่สแต็ก +4 ตลอดระยะเวลาของการทำงาน
ถ้าฉันเพิ่มฟังก์ชันดัมมี่ในแอสเซมเบลอร์
.thumb_func
.globl dummy
dummy:
bx lr
แล้วเรียกมันว่า
void dummy ( unsigned int );
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
dummy(y);
return(0);
}
การเปลี่ยนแปลงการส่งออก
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f804 bl 20 <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <dummy>:
1c: 4770 bx lr
...
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
ตอนนี้เรามีฟังก์ชั่นที่ซ้อนกันฟังก์ชัน notmain จำเป็นต้องเก็บรักษาที่อยู่ผู้ส่งคืนเพื่อที่จะสามารถปิดกั้นที่อยู่ผู้ส่งคืนสำหรับการเรียกซ้อน นี่เป็นเพราะแขนใช้รีจิสเตอร์สำหรับการส่งคืนถ้าใช้สแต็กเช่นพูด x86 หรืออื่น ๆ ได้ดี ... มันจะยังคงใช้สแต็ก แต่ต่างกัน ตอนนี้คุณถามว่าทำไมมันถึงกด r4 การประชุมที่เรียกไม่นานมานี้ได้เปลี่ยนไปเพื่อให้สแต็คอยู่ในขอบเขต 64 บิต (สองคำ) แทน 32 บิตซึ่งเป็นหนึ่งคำ ดังนั้นพวกเขาจำเป็นต้องผลักดันบางสิ่งบางอย่างเพื่อให้สแต็คอยู่ในแนวเดียวกันดังนั้นคอมไพเลอร์เลือกพล r4 ด้วยเหตุผลบางอย่างโดยไม่คำนึงว่าทำไม การเปิดเข้าสู่ r4 จะเป็นข้อผิดพลาดแม้ว่าตามแผนการโทรสำหรับเป้าหมายนี้เราไม่ปิดบัง r4 ในการเรียกใช้ฟังก์ชันเราสามารถกด r0 ถึง r3 ได้ r0 คือค่าส่งคืน ดูเหมือนว่ามันจะทำการปรับหางให้เหมาะสม
แต่เราเห็นว่าคณิตศาสตร์ x และ y ได้รับการปรับให้เหมาะกับค่า hardcoded ของ 2 ที่ส่งผ่านไปยังฟังก์ชันดัมมี่ (ดัมมี่ได้รับการเข้ารหัสโดยเฉพาะในไฟล์แยกต่างหากในกรณีนี้ asm ดังนั้นคอมไพเลอร์จะไม่ปรับการเรียกฟังก์ชันทั้งหมดอย่างสมบูรณ์ หากฉันมีฟังก์ชั่นดัมมี่ที่เพิ่งกลับมาใน C ใน notmain.c เครื่องมือเพิ่มประสิทธิภาพจะลบการเรียกใช้ฟังก์ชัน x, y และดัมมี่เพราะพวกเขาเป็นรหัสที่ไม่ทำงาน / ไร้ประโยชน์ทั้งหมด)
นอกจากนี้โปรดทราบว่าเนื่องจากรหัส flash.s มีขนาดใหญ่ขึ้นเป็นอย่างอื่นและ toolchain ได้ดูแลการแก้ไขที่อยู่ทั้งหมดสำหรับเราดังนั้นเราจึงไม่ต้องทำด้วยตนเอง
เสียงดังกราวที่ไม่ได้เพิ่มประสิทธิภาพสำหรับการอ้างอิง
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: b082 sub sp, #8
26: 2001 movs r0, #1
28: 9001 str r0, [sp, #4]
2a: 2002 movs r0, #2
2c: 9000 str r0, [sp, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: b002 add sp, #8
36: bd80 pop {r7, pc}
เสียงดังกราวที่ดีที่สุด
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 2002 movs r0, #2
26: f7ff fff9 bl 1c <dummy>
2a: 2000 movs r0, #0
2c: bd80 pop {r7, pc}
ผู้เขียนคอมไพเลอร์นั้นเลือกที่จะใช้ r7 เป็นตัวแปรดัมมี่เพื่อจัดเรียงสแต็กและมันกำลังสร้างตัวชี้เฟรมโดยใช้ r7 แม้ว่ามันจะไม่มีอะไรในสแต็กเฟรม โดยทั่วไปการเรียนการสอนอาจได้รับการปรับให้เหมาะสม แต่มันใช้ป็อปอัพเพื่อส่งคืนคำสั่งไม่ใช่สามคำสั่งนั่นอาจเป็นฉันฉันพนันได้เลยว่าฉันจะได้รับ gcc ด้วยตัวเลือกบรรทัดคำสั่งที่ถูกต้อง (ระบุโปรเซสเซอร์)
ส่วนใหญ่ควรตอบคำถามที่เหลือของคุณ
void dummy ( unsigned int );
unsigned int x=1;
unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
ตอนนี้ฉันมีลูกกลม ดังนั้นพวกเขาไปใน. data หรือ. bss หากพวกเขาไม่ได้รับการเพิ่มประสิทธิภาพ
ก่อนที่เราจะดูที่ผลลัพธ์สุดท้ายให้ดูที่วัตถุที่มีการลดขนาด
00000000 <notmain>:
0: b510 push {r4, lr}
2: 4b05 ldr r3, [pc, #20] ; (18 <notmain+0x18>)
4: 6818 ldr r0, [r3, #0]
6: 4b05 ldr r3, [pc, #20] ; (1c <notmain+0x1c>)
8: 3001 adds r0, #1
a: 6018 str r0, [r3, #0]
c: f7ff fffe bl 0 <dummy>
10: 2000 movs r0, #0
12: bc10 pop {r4}
14: bc02 pop {r1}
16: 4708 bx r1
...
Disassembly of section .data:
00000000 <x>:
0: 00000001 andeq r0, r0, r1
ตอนนี้มีข้อมูลที่ขาดหายไปจากสิ่งนี้ แต่มันให้ความคิดว่าเกิดอะไรขึ้นตัวเชื่อมโยงคือสิ่งที่นำวัตถุและเชื่อมโยงวัตถุเหล่านั้นเข้าด้วยกันพร้อมกับข้อมูลที่ให้ไว้ (ในกรณีนี้คือ flash.ld) ที่บอกว่า ข้อมูลและไปเช่นนั้น คอมไพเลอร์ไม่ทราบว่าสิ่งต่าง ๆ มันสามารถมุ่งเน้นไปที่รหัสที่นำเสนอภายนอกใด ๆ ก็ต้องออกจากหลุมสำหรับลิงเกอร์เพื่อเติมเต็มในการเชื่อมต่อ ข้อมูลใด ๆ ที่จะต้องมีวิธีการเชื่อมโยงสิ่งเหล่านั้นเข้าด้วยกันดังนั้นที่อยู่สำหรับทุกสิ่งที่เป็นศูนย์อยู่ที่นี่เพียงเพราะคอมไพเลอร์และตัวแทนจำหน่ายนี้ไม่ทราบ มีข้อมูลอื่นที่ไม่ได้แสดงไว้ที่นี่ที่ผู้เชื่อมโยงใช้เพื่อวางสิ่งต่าง ๆ รหัสที่นี่เป็นตำแหน่งที่เป็นอิสระเพียงพอเพื่อให้ลิงเกอร์สามารถทำงานได้
จากนั้นเราจะเห็นการถอดแยกชิ้นส่วนของเอาต์พุตที่เชื่อมโยงอย่างน้อย
00000020 <notmain>:
20: b510 push {r4, lr}
22: 4b05 ldr r3, [pc, #20] ; (38 <notmain+0x18>)
24: 6818 ldr r0, [r3, #0]
26: 4b05 ldr r3, [pc, #20] ; (3c <notmain+0x1c>)
28: 3001 adds r0, #1
2a: 6018 str r0, [r3, #0]
2c: f7ff fff6 bl 1c <dummy>
30: 2000 movs r0, #0
32: bc10 pop {r4}
34: bc02 pop {r1}
36: 4708 bx r1
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
Disassembly of section .bss:
20000000 <y>:
20000000: 00000000 andeq r0, r0, r0
Disassembly of section .data:
20000004 <x>:
20000004: 00000001 andeq r0, r0, r1
คอมไพเลอร์ได้ขอตัวแปร 32 บิตสองตัวในหน่วยความจำ หนึ่งอยู่ใน. bss เพราะฉันไม่ได้เริ่มต้นมันจึงสันนิษฐานว่าจะเริ่มต้นเป็นศูนย์ อีกอันคือ. data เพราะฉันได้ทำการ initialize มันในการประกาศ
ตอนนี้เพราะสิ่งเหล่านี้เป็นตัวแปรทั่วโลกมันจะสันนิษฐานว่าฟังก์ชั่นอื่น ๆ สามารถแก้ไขได้ คอมไพเลอร์ไม่ได้ตั้งสมมติฐานว่าเมื่อใดที่สามารถเรียกใช้งานหลักได้ดังนั้นจึงไม่สามารถปรับให้เหมาะสมกับสิ่งที่สามารถมองเห็นได้คณิตศาสตร์ y = x + 1 ดังนั้นจึงต้องดำเนินการรันไทม์นั้น มันต้องอ่านจาก ram ทั้งสองตัวแปรเพิ่มและบันทึกกลับมา
ตอนนี้เห็นได้ชัดว่ารหัสนี้จะไม่ทำงาน ทำไม? เพราะ bootstrap ของฉันตามที่แสดงในที่นี้ไม่ได้เตรียม ram ก่อนที่จะโทรไม่สำคัญดังนั้นขยะใด ๆ ที่อยู่ใน 0x20000000 และ 0x20000004 เมื่อชิปตื่นขึ้นมาคือสิ่งที่จะใช้สำหรับ y และ x
จะไม่แสดงให้เห็นว่าที่นี่ คุณสามารถอ่านการท่องไปใน. data และ. bbs ได้อีกต่อไปและทำไมฉันถึงไม่ต้องการมันในรหัสโลหะเปลือยของฉัน แต่ถ้าคุณรู้สึกว่าคุณต้องทำและต้องการฝึกฝนเครื่องมือมากกว่าที่จะให้คนอื่นทำถูกต้อง .. .
https://github.com/dwelch67/raspberrypi/tree/master/bssdata
สคริปต์ linker และ bootstraps ค่อนข้างเจาะจงเฉพาะคอมไพเลอร์ดังนั้นทุกสิ่งที่คุณเรียนรู้เกี่ยวกับคอมไพเลอร์หนึ่งเวอร์ชันสามารถถูกโยนในเวอร์ชันถัดไปหรือกับคอมไพเลอร์อื่น ๆ ได้ แต่อีกเหตุผลหนึ่งที่ฉันไม่ต้องใช้ความพยายามมากมาย เพียงเพื่อจะขี้เกียจนี้:
unsigned int x=1;
ฉันจะทำสิ่งนี้มากกว่า
unsigned int x;
...
x = 1;
และให้คอมไพเลอร์ใส่ไว้ในข้อความสำหรับฉัน บางครั้งมันช่วยประหยัดแฟลชด้วยวิธีนี้บางครั้งก็เผาผลาญได้มากขึ้น แน่นอนที่สุดคือโปรแกรมและพอร์ตจาก toolchain หรือคอมไพเลอร์หนึ่งไปยังอีกอย่างแน่นอน น่าเชื่อถือมากขึ้นและมีข้อผิดพลาดน้อยลง ใช่ไม่เป็นไปตามมาตรฐาน C
ตอนนี้ถ้าเราสร้างกาแลกซี่ทรงกลมเหล่านี้ล่ะ?
void dummy ( unsigned int );
static unsigned int x=1;
static unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
ดี
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
เห็นได้ชัดว่าตัวแปรเหล่านั้นไม่สามารถแก้ไขได้ด้วยรหัสอื่นดังนั้นตอนนี้คอมไพเลอร์สามารถรวบรวมเวลาที่ปรับแต่งรหัสตายได้อย่างที่เคยทำมาก่อน
ไม่ได้เพิ่มประสิทธิภาพ
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 4804 ldr r0, [pc, #16] ; (38 <notmain+0x18>)
26: 6800 ldr r0, [r0, #0]
28: 1c40 adds r0, r0, #1
2a: 4904 ldr r1, [pc, #16] ; (3c <notmain+0x1c>)
2c: 6008 str r0, [r1, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: bd80 pop {r7, pc}
36: 46c0 nop ; (mov r8, r8)
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
คอมไพเลอร์นี้ซึ่งใช้สแต็กสำหรับคนในท้องถิ่นตอนนี้ใช้ ram สำหรับ globals และรหัสนี้ตามที่เขียนไว้เพราะฉันไม่ได้จัดการ. data หรือ. bbs อย่างถูกต้อง
และสิ่งสุดท้ายที่เราไม่สามารถเห็นได้ในการถอดชิ้นส่วน
:1000000000100020150000001B0000001B00000075
:100010001B00000000F004F8FFE7FEE77047000057
:1000200080B500AF04480068401C04490860FFF731
:10003000F5FF002080BDC046040000200000002025
:08004000E0FFFF7F010000005A
:0400480078563412A0
:00000001FF
ฉันเปลี่ยน x เป็น pre-init ด้วย 0x12345678 สคริปต์ตัวเชื่อมโยงของฉัน (สำหรับ gnu ld) นี้มีสิ่งที่บ๊อบ ที่บอก linker ฉันต้องการให้สถานที่สุดท้ายที่จะอยู่ในพื้นที่ที่อยู่เท็ด แต่เก็บไว้ในไบนารีในพื้นที่ที่อยู่เท็ดและใครบางคนจะย้ายมันสำหรับคุณ และเราสามารถเห็นสิ่งที่เกิดขึ้น นี่คือรูปแบบฐานสิบหกของ Intel และเราสามารถเห็น 0x12345678
:0400480078563412A0
อยู่ในพื้นที่ที่อยู่แฟลชของไบนารี
readelf ยังแสดงสิ่งนี้
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
EXIDX 0x010040 0x00000040 0x00000040 0x00008 0x00008 R 0x4
LOAD 0x010000 0x00000000 0x00000000 0x00048 0x00048 R E 0x10000
LOAD 0x020004 0x20000004 0x00000048 0x00004 0x00004 RW 0x10000
LOAD 0x030000 0x20000000 0x20000000 0x00000 0x00004 RW 0x10000
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
บรรทัด LOAD ที่ที่อยู่เสมือนคือ 0x20000004 และฟิสิคัลคือ 0x48