บูตไมโครคอนโทรลเลอร์และการเริ่มต้นอย่างไรทีละขั้นตอน?


17

เมื่อเขียนรหัส C รวบรวมและอัปโหลดไปยังไมโครคอนโทรลเลอร์ไมโครคอนโทรลเลอร์จะเริ่มทำงาน แต่ถ้าเราใช้ขั้นตอนการอัพโหลดและเริ่มต้นทีละน้อยในแบบสโลว์โมชั่นฉันมีความสับสนเกี่ยวกับสิ่งที่เกิดขึ้นจริงภายใน MCU (หน่วยความจำ, CPU, bootloader) นี่คือสิ่งที่ฉันจะตอบถ้าใครบางคนถามฉัน:

  1. รหัสไบนารีที่คอมไพล์แล้วเขียนไปยังแฟลช ROM (หรือ EEPROM) ผ่าน USB
  2. Bootloader คัดลอกบางส่วนของรหัสนี้ไปยัง RAM หากเป็นจริงบูทโหลดเดอร์จะรู้วิธีการคัดลอก (ส่วนใดของ ROM ที่จะคัดลอกไปยัง RAM)
  3. CPU เริ่มดึงคำแนะนำและข้อมูลของรหัสจาก ROM และ RAM

มันผิดหรือเปล่า?

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

ฉันได้พบคำอธิบายพื้นฐานมากมายเกี่ยวกับวิธีบู๊ตพีซีผ่าน BIOS แต่ฉันติดอยู่กับกระบวนการเริ่มต้นของไมโครคอนโทรลเลอร์

คำตอบ:


31

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


ในตอนแรกฉันมีภาพสิ่งต่าง ๆ ที่เบลอสองภาพ:
user16307

1. ) "กรณีใช้งานหลักคือให้มีคำแนะนำในรอม / แฟลชและข้อมูลในหน่วยความจำ" เมื่อคุณพูดว่า "data in RAM here" คุณหมายถึงข้อมูลที่ถูกบันทึกไว้ในการเข้าร่วมโปรแกรมหรือไม่ หรือคุณยังรวมถึงข้อมูลเริ่มต้น ฉันหมายถึงเมื่อเราอัปโหลดรหัสไปยัง ROM มีข้อมูลที่เริ่มต้นแล้วในรหัสของเรา เช่นใน oode ของเราถ้าเรามี: int x = 1; int y = x +1; โค้ดด้านบนมีคำแนะนำและมีข้อมูลเริ่มต้นซึ่งคือ 1 (x = 1) ข้อมูลนี้ถูกคัดลอกไปยัง RAM หรือยังอยู่ใน ROM เท่านั้น
user16307

13
ฮ่าฮ่าตอนนี้ฉันรู้จำนวนอักขระสูงสุดสำหรับการแลกเปลี่ยนคำตอบแล้ว
old_timer

2
คุณควรเขียนหนังสือที่อธิบายแนวคิดดังกล่าวให้กับมือใหม่ "ฉันมีตัวอย่าง zillion ที่ github" - เป็นไปได้ไหมที่จะแบ่งปันตัวอย่างเล็ก ๆ น้อย ๆ
AkshayImmanuelD

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

8

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

นี่คือกระบวนการบูตพื้นฐาน ฉันจะตั้งชื่อรูปแบบทั่วไปบางส่วน แต่ส่วนใหญ่ฉันใช้ง่ายนี้

  1. รีเซ็ต:มีประเภทพื้นฐานสองประเภท อันแรกคือการเปิดเครื่องใหม่ซึ่งสร้างขึ้นภายในขณะที่แรงดันไฟฟ้าของอุปทานเพิ่มขึ้น ประการที่สองคือการสลับขาภายนอก การรีเซ็ตจะบังคับให้ flip-flop ทั้งหมดใน MCU เข้าสู่สถานะที่กำหนดไว้ล่วงหน้า

  2. การเริ่มต้นฮาร์ดแวร์เพิ่มเติม:อาจต้องใช้เวลาเพิ่มเติมและ / หรือรอบนาฬิกาก่อนที่ CPU จะเริ่มทำงาน ตัวอย่างเช่นใน TI MCUs ที่ฉันทำงานมีห่วงโซ่การสแกนการกำหนดค่าภายในที่โหลด

  3. CPU boot: CPU ดึงคำสั่งแรกจากที่อยู่พิเศษที่เรียกว่าเวกเตอร์รีเซ็ต ที่อยู่นี้จะถูกกำหนดเมื่อ CPU ถูกออกแบบ จากตรงนั้นมันเป็นเพียงการเรียกใช้โปรแกรมปกติ

    CPU ทำซ้ำสามขั้นตอนพื้นฐานซ้ำแล้วซ้ำอีก:

    • Fetch: อ่านคำสั่ง (ค่า 8-16 หรือ 32- บิต) จากที่อยู่ที่เก็บไว้ในการลงทะเบียนโปรแกรมเคาน์เตอร์ (PC) จากนั้นเพิ่มพีซี
    • ถอดรหัส: แปลงคำสั่งไบนารีเป็นชุดของค่าสำหรับสัญญาณควบคุมภายในของ CPU
    • ดำเนินการ: ทำตามคำสั่ง - เพิ่มการลงทะเบียนสองรายการอ่านหรือเขียนไปยังหน่วยความจำสาขา (เปลี่ยนพีซี) หรืออะไรก็ตาม

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

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

    ซีพียูที่มีสถาปัตยกรรมของ von Neumannมีบัสเดียวที่ใช้สำหรับทั้งคำสั่งและข้อมูล ซีพียูที่มีสถาปัตยกรรมฮาร์วาร์ดจะมีหนึ่งบัสสำหรับคำแนะนำและอีกหนึ่งสำหรับข้อมูล ใน MCUs จริงบัสทั้งสองนี้อาจเชื่อมต่อกับความทรงจำเดียวกันดังนั้นบ่อยครั้งที่ (แต่ไม่เสมอไป) บางสิ่งที่คุณไม่ต้องกังวล

    กลับไปที่ขั้นตอนการบู๊ต หลังจากรีเซ็ตพีซีจะถูกโหลดด้วยค่าเริ่มต้นที่เรียกว่ารีเซ็ตเวกเตอร์ สิ่งนี้สามารถสร้างขึ้นในฮาร์ดแวร์หรือ (ในซีพียู ARM Cortex-M) มันสามารถอ่านออกจากหน่วยความจำโดยอัตโนมัติ CPU ดึงคำแนะนำจากเวกเตอร์รีเซ็ตและเริ่มวนซ้ำตามขั้นตอนข้างต้น ณ จุดนี้ซีพียูจะดำเนินการตามปกติ

  4. ตัวโหลดการบูต:มักจะมีการตั้งค่าระดับต่ำที่จำเป็นต้องทำเพื่อให้ส่วนที่เหลือของการทำงานของ MCU ซึ่งอาจรวมถึงสิ่งต่าง ๆ เช่นการล้าง RAM และการโหลดการตั้งค่าการตัดแต่งการผลิตสำหรับส่วนประกอบอะนาล็อก อาจมีตัวเลือกในการโหลดรหัสจากแหล่งภายนอกเช่นพอร์ตอนุกรมหรือหน่วยความจำภายนอก MCU อาจรวมถึงboot ROMที่มีโปรแกรมขนาดเล็กเพื่อทำสิ่งเหล่านี้ ในกรณีนี้ซีพียูรีเซ็ตเวกเตอร์ชี้ไปที่พื้นที่ที่อยู่ของ boot ROM นี่เป็นรหัสปกติโดยผู้ผลิตให้คุณไม่ต้องเขียนเอง :-) ในพีซี BIOS นั้นเทียบเท่ากับ boot ROM

  5. การตั้งค่าสภาพแวดล้อม C: C คาดว่าจะมีสแต็ค (พื้นที่ RAM สำหรับการจัดเก็บสถานะระหว่างการเรียกใช้ฟังก์ชัน) และตำแหน่งหน่วยความจำที่เริ่มต้นสำหรับตัวแปรทั่วโลก เหล่านี้คือส่วน. stack, .data และ. bbs ที่ Dwelch กำลังพูดถึง ตัวแปรโกลบอลที่เตรียมข้อมูลเบื้องต้นมีค่าเริ่มต้นที่คัดลอกจากแฟลชไปยัง RAM ในขั้นตอนนี้ ตัวแปรโกลบอลแบบไม่กำหนดค่าเริ่มต้นมีที่อยู่ RAM ที่อยู่ติดกันดังนั้นบล็อกทั้งหมดของหน่วยความจำจึงสามารถเริ่มต้นได้อย่างง่ายดาย สแต็กไม่จำเป็นต้องเริ่มต้น (แม้ว่าจะเป็นได้) - สิ่งที่คุณต้องทำจริงๆคือตั้งค่าตัวชี้สแต็กของ CPU เพื่อให้ชี้ไปยังพื้นที่ที่กำหนดใน RAM

  6. ฟังก์ชั่นหลัก : เมื่อตั้งค่าสภาพแวดล้อม C แล้วตัวโหลด C จะเรียกใช้ฟังก์ชัน main () นี่คือจุดเริ่มต้นของรหัสแอปพลิเคชันของคุณ หากคุณต้องการคุณสามารถออกจากไลบรารีมาตรฐานข้ามการตั้งค่าสภาพแวดล้อม C และเขียนรหัสของคุณเองเพื่อโทรไปที่ main () MCU บางตัวอาจให้คุณเขียนบูทโหลดเดอร์ของคุณเองจากนั้นคุณสามารถตั้งค่าระดับต่ำทั้งหมดด้วยตัวเอง

สิ่งอื่น ๆ : MCUs จำนวนมากจะให้คุณเรียกใช้โค้ดจาก RAM เพื่อประสิทธิภาพที่ดีขึ้น โดยปกติจะตั้งค่าในการกำหนดค่าตัวเชื่อมโยง ตัวเชื่อมโยงกำหนดสองที่อยู่ให้กับทุกฟังก์ชั่น - ที่อยู่โหลดซึ่งเป็นที่เก็บรหัสแรก (โดยทั่วไปคือแฟลช) และที่อยู่เรียกใช้ซึ่งเป็นที่อยู่ที่โหลดลงในพีซีเพื่อเรียกใช้ฟังก์ชัน (แฟลชหรือ RAM) ในการเรียกใช้โค้ดจาก RAM คุณเขียนโค้ดเพื่อให้ CPU คัดลอกโค้ดฟังก์ชั่นจากที่อยู่โหลดในแฟลชไปยังที่อยู่ที่ทำงานใน RAM จากนั้นเรียกใช้ฟังก์ชันที่ที่อยู่รัน ตัวเชื่อมโยงสามารถกำหนดตัวแปรโกลบอลเพื่อช่วยในเรื่องนี้ แต่การเรียกใช้โค้ดจาก RAM เป็นตัวเลือกใน MCU โดยปกติคุณจะทำก็ต่อเมื่อคุณต้องการประสิทธิภาพสูงจริงๆหรือถ้าคุณต้องการเขียนแฟลชซ้ำ


1

สรุปของคุณจะอยู่ที่ประมาณที่ถูกต้องสำหรับสถาปัตยกรรม Von Neumann โดยทั่วไปแล้วรหัสเริ่มต้นจะถูกโหลดเข้าสู่ RAM ผ่าน bootloader แต่ไม่ใช่ (โดยทั่วไป) bootloader ซอฟต์แวร์ที่คำทั่วไปอ้างถึง โดยปกติจะเป็นพฤติกรรม 'อบเข้าไปในซิลิคอน' การประมวลผลโค้ดในสถาปัตยกรรมนี้มักจะเกี่ยวข้องกับการแคชแบบทำนายคำสั่งจาก ROM ในลักษณะที่โปรเซสเซอร์ใช้เวลาในการประมวลผลโค้ดให้มากที่สุดและไม่ต้องรอให้โหลดโค้ดลงใน RAM ฉันได้อ่านบางที่ว่า MSP430 เป็นตัวอย่างของสถาปัตยกรรมนี้

ในอุปกรณ์Harvard Architectureคำสั่งถูกเรียกใช้งานโดยตรงจาก ROM ในขณะที่เข้าถึงหน่วยความจำข้อมูล (RAM) ผ่านบัสแยกต่างหาก ในสถาปัตยกรรมนี้รหัสก็เริ่มดำเนินการจากเวกเตอร์รีเซ็ต PIC24 และ dsPIC33 เป็นตัวอย่างของสถาปัตยกรรมนี้

สำหรับการพลิกบิตที่เกิดขึ้นจริงกระบวนการเหล่านี้อาจแตกต่างจากอุปกรณ์ต่ออุปกรณ์และสามารถเกี่ยวข้องกับ debuggers, JTAG, วิธีการที่เป็นกรรมสิทธิ์ ฯลฯ


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

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